mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-11-16 22:23:45 +08:00
mod
This commit is contained in:
202
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/37丨数据采集实战:如何自动化运营微博?.md
Normal file
202
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/37丨数据采集实战:如何自动化运营微博?.md
Normal file
@@ -0,0 +1,202 @@
|
||||
<audio id="audio" title="37丨数据采集实战:如何自动化运营微博?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/78/aa/7828b7dee7210e9a89ab6cabb8a2bbaa.mp3"></audio>
|
||||
|
||||
今天我来带你做一个数据采集的实战。之前我讲到可以使用第三方工具,比如八爪鱼做数据采集,也可以自己编写脚本,比如使用Python。编写Python做数据采集和自动化最大的好处就是可控性强,每个步骤都可以调试,而且可以找到问题所在并一一突破。
|
||||
|
||||
今天我就带你使用Python自动化运营微博。关于今天的学习,希望你能达成以下的3个学习目标:
|
||||
|
||||
<li>
|
||||
掌握Selenium自动化测试工具,以及元素定位的方法;
|
||||
</li>
|
||||
<li>
|
||||
学会编写微博自动化功能模块:加关注,写评论,发微博;
|
||||
</li>
|
||||
<li>
|
||||
对微博自动化做自我总结。
|
||||
</li>
|
||||
|
||||
## Selenium自动化测试工具
|
||||
|
||||
当我们做Web自动化测试的时候,可以选用Selenium或者Puppeteer工具。我在[第10篇](https://time.geekbang.org/column/article/76001)的时候简单介绍过Selenium这个工具,你可以再回顾一下。Puppeteer通过控制无头Chrome来完成浏览器的工作。这两个工具之间的区别在于:Selenium更关注程序执行的流程本身,比如找到指定的元素,设置相应的值,然后点击操作。而Puppeteer是浏览者的视角,比如光标移动到某个元素上,键盘输入某个内容等。
|
||||
|
||||
今天我们继续使用Selenium工具自动化模拟浏览器,重点是学习对元素的定位。在第10篇讲到Selenium WebDriver的使用时,重点是对HTML进行获取和解析,然后通过HTML中的XPath进行提取,读取相应的内容。
|
||||
|
||||
在今天的实战里,我们需要在微博上自动登录,加关注,发评论,发微博等,这些操作都需要在浏览器上完成,所以我们可以使用Webdriver自带的元素定位功能。
|
||||
|
||||
如果我们想定位一个元素,可以通过id、name、class、tag、链接上的全部文本、链接上的部分文本、XPath或者CSS进行定位,在Selenium Webdriver中也提供了这8种方法方便我们定位元素。
|
||||
|
||||
<li>
|
||||
通过id定位:我们可以使用find_element_by_id()函数。比如我们想定位id=loginName的元素,就可以使用browser.find_element_by_id(“loginName”)。
|
||||
</li>
|
||||
<li>
|
||||
通过name定位:我们可以使用find_element_by_name()函数,比如我们想要对name=key_word的元素进行定位,就可以使用browser.find_element_by_name(“key_word”)。
|
||||
</li>
|
||||
<li>
|
||||
通过class定位:可以使用find_element_by_class_name()函数。
|
||||
</li>
|
||||
<li>
|
||||
通过tag定位:使用find_element_by_tag_name()函数。
|
||||
</li>
|
||||
<li>
|
||||
通过link上的完整文本定位:使用find_element_by_link_text()函数。
|
||||
</li>
|
||||
<li>
|
||||
通过link上的部分文本定位:使用find_element_by_partial_link_text()函数。有时候超链接上的文本很长,我们通过查找部分文本内容就可以定位。
|
||||
</li>
|
||||
<li>
|
||||
通过XPath定位:使用find_element_by_xpath()函数。使用XPath定位的通用性比较好,因为当id、name、class为多个,或者元素没有这些属性值的时候,XPath定位可以帮我们完成任务。
|
||||
</li>
|
||||
<li>
|
||||
通过CSS定位:使用find_element_by_css_selector()函数。CSS定位也是常用的定位方法,相比于XPath来说更简洁。
|
||||
</li>
|
||||
|
||||
在我们获取某个元素之后,就可以对这个元素进行操作了,对元素进行的操作包括:
|
||||
|
||||
1. 清空输入框的内容:使用clear()函数;
|
||||
1. 在输入框中输入内容:使用send_keys(content)函数传入要输入的文本;
|
||||
1. 点击按钮:使用click()函数,如果元素是个按钮或者链接的时候,可以点击操作;
|
||||
1. 提交表单:使用submit()函数,元素对象为一个表单的时候,可以提交表单;
|
||||
|
||||
了解WebDriver元素定位和功能之后,我们模拟一下微博的自动登录,具体代码如下:
|
||||
|
||||
```
|
||||
from selenium import webdriver
|
||||
import time
|
||||
browser = webdriver.Chrome()
|
||||
# 登录微博
|
||||
def weibo_login(username, password):
|
||||
# 打开微博登录页
|
||||
browser.get('https://passport.weibo.cn/signin/login')
|
||||
browser.implicitly_wait(5)
|
||||
time.sleep(1)
|
||||
# 填写登录信息:用户名、密码
|
||||
browser.find_element_by_id("loginName").send_keys(username)
|
||||
browser.find_element_by_id("loginPassword").send_keys(password)
|
||||
time.sleep(1)
|
||||
# 点击登录
|
||||
browser.find_element_by_id("loginAction").click()
|
||||
time.sleep(1)
|
||||
# 设置用户名、密码
|
||||
username = 'XXXX'
|
||||
password = "XXXX"
|
||||
weibo_login(username, password)
|
||||
|
||||
```
|
||||
|
||||
需要说明的是,你需要填写自己的微博用户名和密码(对应username和password)。
|
||||
|
||||
通过HTML标签分析,我们能看到id=loginName和id=loginPassword对应着用户名和密码的输入框,获取这两个元素之后,使用send_keys()设置用户名和密码,然后通过id=loginAction找到登录按钮,获取该元素,使用click()函数点击提交。
|
||||
|
||||
这样我们就完成了微博的自动化登录,具体效果如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/38/71/380827a5713e41896dfe5436a4589471.gif" alt="">
|
||||
|
||||
## 微博自动化运营:加关注,写评论,发微博
|
||||
|
||||
我们已经基本了解了Selenium WebDriver是如何获取指定元素,并且对元素进行操作的。下面我们撰写一下微博自动加关注,写评论,发微博的功能代码,具体模块如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/61/4e/61ed79746527371c7bf1c173355f6c4e.jpg" alt=""><br>
|
||||
我们刚才完成了weibo_login微博登录模块的编写,现在来看下加关注的功能,具体代码如下:
|
||||
|
||||
```
|
||||
# 添加指定的用户
|
||||
def add_follow(uid):
|
||||
browser.get('https://m.weibo.com/u/'+str(uid))
|
||||
time.sleep(1)
|
||||
#browser.find_element_by_id("follow").click()
|
||||
follow_button = browser.find_element_by_xpath('//div[@class="m-add-box m-followBtn"]')
|
||||
follow_button.click()
|
||||
time.sleep(1)
|
||||
# 选择分组
|
||||
group_button = browser.find_element_by_xpath('//div[@class="m-btn m-btn-white m-btn-text-black"]')
|
||||
group_button.click()
|
||||
time.sleep(1)
|
||||
# 每天学点心理学UID
|
||||
uid = '1890826225'
|
||||
add_follow(uid)
|
||||
|
||||
```
|
||||
|
||||
这里有两点你需要注意。
|
||||
|
||||
第一点是如何找到用户的UID呢?
|
||||
|
||||
在微博中,用户是用UID做唯一标识的。代码中我随机指定了一个UID,你也可以指定其他的UID。那么如何获取用户的UID的呢?
|
||||
|
||||
你可以点击任何一个微博用户,查看他的URL链接,比如链接是[https://weibo.com/u/5020181423](https://weibo.com/u/5020181423) ,那么u后面的数字5020181423即为用户的UID。你也可能遇到[https://weibo.com/ixinli](https://weibo.com/ixinli)(每天学点心理学)这样的链接情况,那么通过查看他的粉丝即可以显示出UID,比如这个微博的粉丝链接是[https://weibo.com/1890826225/fans](https://weibo.com/1890826225/fans),那么UID对应的就是1890826225。
|
||||
|
||||
第二个需要注意的是使用XPath定位元素定位。
|
||||
|
||||
我们如何找到“关注”这个按钮的元素标识呢?在Chrome浏览器中,在“关注”按钮用鼠标右键点击,选择“检查”查看这个元素对应的代码。通过分析,你能看到这个元素的div标签中的class属性为m-add-box m-followBtn,那么你可以通过find_element_by_xpath(’//div[@class=“m-add-box m-followBtn”]’)获取这个元素。关注之后,程序会弹出选择分组的页面,如下所示。
|
||||
|
||||
通过同样的方法,你可以查看“取消”这个按钮对应的HTML标签特征,然后通过find_element_by_xpath定位,使用click()函数提交。这样我们就关注了一个指定的用户。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/8e/0a/8ed67303e83dd8e7aee9338802c2cb0a.png" alt=""><br>
|
||||
接下来,我们继续完成写评论和发微博的模块代码:
|
||||
|
||||
```
|
||||
# 给指定某条微博添加内容
|
||||
def add_comment(weibo_url, content):
|
||||
browser.get(weibo_url)
|
||||
browser.implicitly_wait(5)
|
||||
content_textarea = browser.find_element_by_css_selector("textarea.W_input").clear()
|
||||
content_textarea = browser.find_element_by_css_selector("textarea.W_input").send_keys(content)
|
||||
time.sleep(2)
|
||||
comment_button = browser.find_element_by_css_selector(".W_btn_a").click()
|
||||
time.sleep(1)
|
||||
|
||||
# 发文字微博
|
||||
def post_weibo(content):
|
||||
# 跳转到用户的首页
|
||||
browser.get('https://weibo.com')
|
||||
browser.implicitly_wait(5)
|
||||
# 点击右上角的发布按钮
|
||||
post_button = browser.find_element_by_css_selector("[node-type='publish']").click()
|
||||
# 在弹出的文本框中输入内容
|
||||
content_textarea = browser.find_element_by_css_selector("textarea.W_input").send_keys(content)
|
||||
time.sleep(2)
|
||||
# 点击发布按钮
|
||||
post_button = browser.find_element_by_css_selector("[node-type='submit']").click()
|
||||
time.sleep(1)
|
||||
# 给指定的微博写评论
|
||||
weibo_url = 'https://weibo.com/1890826225/HjjqSahwl'
|
||||
content = 'Gook Luck!好运已上路!'
|
||||
# 自动发微博
|
||||
content = '每天学点心理学'
|
||||
post_weibo(content)
|
||||
|
||||
```
|
||||
|
||||
这个环节里,同样也有一些需要说明的地方。
|
||||
|
||||
如何找到某条微博的链接呢?你可以在某个用户微博页面中点击时间的链接,这样就可以获取这条微博的链接。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/63/a9/633528f33648263152cd92b353030ba9.png" alt=""><br>
|
||||
如何定位评论区中输入框元素的位置呢?
|
||||
|
||||
我们将鼠标移动到评论框中,在Chrome浏览器中点击右键“查看”,可以看到这个textarea的class是W_input,使用find_element_by_css_selector函数进行定位,然后通过send_keys设置内容。
|
||||
|
||||
最后就是发微博的流程。我们可以观察到点击微博页面的右上角的按钮后,会弹出一个发微博的文本框,设置好内容,点击发送即可。发微博的函数和写评论的代码类似,使用find_element_by_css_selector()函数定位,通过send_keys()设置内容的设置,最后通过click()发送。
|
||||
|
||||
上面的代码你可以自己模拟下,在实际运行过程中,你可能会遇到各种情况,比如下面这种情况:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/14/04/1453a3253b24aba642b6bf477b5add04.png" alt=""><br>
|
||||
微博自动化运营是一个系统的工程,需要考虑到各种情况,比如相同的内容发布需要间隔10分钟以上;关注了一个用户之后,就无法对他二次关注,可以判断是否已经关注过,再关注操作;因为操作频繁导致需要输入验证码的情况等。
|
||||
|
||||
另外,微博自动化运营只是自动化运营的开始,实际上在微信上我们也可以做同样的操作。比如监听微信群的消息,自动发微信等。实际要考虑的问题比微博的要复杂。
|
||||
|
||||
## 总结
|
||||
|
||||
今天我带你进行了微博自动化运营的实战,通过实战你对使用Selenium WebDriver应该更有了解,包括如何定位指定的元素,对元素进行各种操作等。同时我们使用了implicitly_wait函数以及time.sleep()函数让浏览器和程序等待一段时间,完成数据加载之后再进行后续的操作,这样就避免了数据没有加载完,导致获取不到指定元素的情况。
|
||||
|
||||
通过三个模块(加关注、写评论、发微博)的编写,你能了解如何使用工具完成自动化的操作。在实际的过程中,可能会遇到各种复杂情况,这些都需要你在运行过程中慢慢体会。
|
||||
|
||||
自动化运营是个细致的活儿,我在之前的加餐文章中也提到过。如果真的想要实现自动化,还需要解决反垃圾的清理问题等,你可以再回顾一下。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/33/d5/33ee64c5a434e1a7093594499e9c05d5.png" alt=""><br>
|
||||
最后留两道题吧。今天我们对微博加关注这一模块编写了代码,同样我们也可能会对某个指定用户的UID做取消关注的操作,请你使用今天讲的元素定位和操作功能,编写相应的代码。
|
||||
|
||||
通过今天自动化测试工具的学习,你有怎样的收获和总结呢?
|
||||
|
||||
欢迎在评论区与我分享你的答案,也欢迎点击”请朋友读“,把这篇文章分享给你的朋友或者同事,一起实战交流一下。
|
||||
|
||||
|
||||
259
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/38丨数据可视化实战:如何给毛不易的歌曲做词云展示?.md
Normal file
259
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/38丨数据可视化实战:如何给毛不易的歌曲做词云展示?.md
Normal file
@@ -0,0 +1,259 @@
|
||||
<audio id="audio" title="38丨数据可视化实战:如何给毛不易的歌曲做词云展示?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/2a/d6/2ac6a698c7a658d6f1122f3f59f999d6.mp3"></audio>
|
||||
|
||||
今天我们做一个数据可视化的项目。
|
||||
|
||||
我们经常需要对分析的数据提取常用词,做词云展示。比如一些互联网公司会抓取用户的画像,或者每日讨论话题的关键词,形成词云并进行展示。再或者,假如你喜欢某个歌手,想了解这个歌手创作的歌曲中经常用到哪些词语,词云就是个很好的工具。最后,只需要将词云生成一张图片就可以直观地看到结果。
|
||||
|
||||
那么在今天的实战项目里,有3个目标需要掌握:
|
||||
|
||||
<li>
|
||||
掌握词云分析工具,并进行可视化呈现;
|
||||
</li>
|
||||
<li>
|
||||
掌握Python爬虫,对网页的数据进行爬取;
|
||||
</li>
|
||||
<li>
|
||||
掌握XPath工具,分析提取想要的元素 。
|
||||
</li>
|
||||
|
||||
## 如何制作词云
|
||||
|
||||
首先我们需要了解什么是词云。词云也叫文字云,它帮助我们统计文本中高频出现的词,过滤掉某些常用词(比如“作曲”“作词”),将文本中的重要关键词进行可视化,方便分析者更好更快地了解文本的重点,同时还具有一定的美观度。
|
||||
|
||||
Python提供了词云工具WordCloud,使用pip install wordcloud安装后,就可以创建一个词云,构造方法如下:
|
||||
|
||||
```
|
||||
wc = WordCloud(
|
||||
background_color='white',# 设置背景颜色
|
||||
mask=backgroud_Image,# 设置背景图片
|
||||
font_path='./SimHei.ttf', # 设置字体,针对中文的情况需要设置中文字体,否则显示乱码
|
||||
max_words=100, # 设置最大的字数
|
||||
stopwords=STOPWORDS,# 设置停用词
|
||||
max_font_size=150,# 设置字体最大值
|
||||
width=2000,# 设置画布的宽度
|
||||
height=1200,# 设置画布的高度
|
||||
random_state=30# 设置多少种随机状态,即多少种颜色
|
||||
)
|
||||
|
||||
```
|
||||
|
||||
实际上WordCloud还有更多的构造参数,代码里展示的是一些主要参数,我在代码中都有注释,你可以自己看下。
|
||||
|
||||
创建好WordCloud类之后,就可以使用wordcloud=generate(text)方法生成词云,传入的参数text代表你要分析的文本,最后使用wordcloud.tofile(“a.jpg”)函数,将得到的词云图像直接保存为图片格式文件。
|
||||
|
||||
你也可以使用Python的可视化工具Matplotlib进行显示,方法如下:
|
||||
|
||||
```
|
||||
import matplotlib.pyplot as plt
|
||||
plt.imshow(wordcloud)
|
||||
plt.axis("off")
|
||||
plt.show()
|
||||
|
||||
```
|
||||
|
||||
需要注意的是,我们不需要显示X轴和Y轴的坐标,使用plt.axis(“off”)可以将坐标轴关闭。
|
||||
|
||||
了解了如何使用词云工具WordCloud之后,我们将专栏前15节的标题进行词云可视化,具体的代码如下:
|
||||
|
||||
```
|
||||
#-*- coding:utf-8 -*-
|
||||
from wordcloud import WordCloud
|
||||
import matplotlib.pyplot as plt
|
||||
import jieba
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
# 生成词云
|
||||
def create_word_cloud(f):
|
||||
print('根据词频计算词云')
|
||||
text = " ".join(jieba.cut(f,cut_all=False, HMM=True))
|
||||
wc = WordCloud(
|
||||
font_path="./SimHei.ttf",
|
||||
max_words=100,
|
||||
width=2000,
|
||||
height=1200,
|
||||
)
|
||||
wordcloud = wc.generate(text)
|
||||
# 写词云图片
|
||||
wordcloud.to_file("wordcloud.jpg")
|
||||
# 显示词云文件
|
||||
plt.imshow(wordcloud)
|
||||
plt.axis("off")
|
||||
plt.show()
|
||||
|
||||
f = '数据分析全景图及修炼指南\
|
||||
学习数据挖掘的最佳学习路径是什么?\
|
||||
Python基础语法:开始你的Python之旅\
|
||||
Python科学计算:NumPy\
|
||||
Python科学计算:Pandas\
|
||||
学习数据分析要掌握哪些基本概念?\
|
||||
用户画像:标签化就是数据的抽象能力\
|
||||
数据采集:如何自动化采集数据?\
|
||||
数据采集:如何用八爪鱼采集微博上的“D&G”评论?\
|
||||
Python爬虫:如何自动化下载王祖贤海报?\
|
||||
数据清洗:数据科学家80%时间都花费在了这里?\
|
||||
数据集成:这些大号一共20亿粉丝?\
|
||||
数据变换:大学成绩要求正态分布合理么?\
|
||||
数据可视化:掌握数据领域的万金油技能\
|
||||
一次学会Python数据可视化的10种技能'
|
||||
|
||||
```
|
||||
|
||||
运行结果:<br>
|
||||
<img src="https://static001.geekbang.org/resource/image/49/34/49b2c6a27345777922db4b6e31110434.png" alt=""><br>
|
||||
你能从结果中看出,还是有一些常用词显示出来了,比如“什么”“要求”“这些”等,我们可以把这些词设置为停用词,编写remove_stop_words函数,从文本中去掉:
|
||||
|
||||
```
|
||||
# 去掉停用词
|
||||
def remove_stop_words(f):
|
||||
stop_words = ['学会', '就是', '什么']
|
||||
for stop_word in stop_words:
|
||||
f = f.replace(stop_word, '')
|
||||
return f
|
||||
|
||||
```
|
||||
|
||||
然后在结算词云前调用f = remove_stop_words(f)方法,最后运行可以得到如下的结果:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d0/ed/d0bd9cde1e1c0638976cef26b519fded.png" alt=""><br>
|
||||
你能看出,去掉停用词之后的词云更加清晰,更能体现前15章的主要内容。
|
||||
|
||||
## 给毛不易的歌词制作词云
|
||||
|
||||
假设我们现在要给毛不易的歌词做个词云,那么需要怎么做呢?我们先把整个项目的流程梳理下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/7c/97/7cff33b392cec653ca2e68fbecd4ef97.jpg" alt="">
|
||||
|
||||
<li>
|
||||
在准备阶段:我们主要使用Python爬虫获取HTML,用XPath对歌曲的ID、名称进行解析,然后通过网易云音乐的API接口获取每首歌的歌词,最后将所有的歌词合并得到一个变量。
|
||||
</li>
|
||||
<li>
|
||||
在词云分析阶段,我们需要创建WordCloud词云类,分析得到的歌词文本,最后可视化。
|
||||
</li>
|
||||
|
||||
基于上面的流程,编写代码如下:
|
||||
|
||||
```
|
||||
# -*- coding:utf-8 -*-
|
||||
# 网易云音乐 通过歌手ID,生成该歌手的词云
|
||||
import requests
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
from wordcloud import WordCloud
|
||||
import matplotlib.pyplot as plt
|
||||
import jieba
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
from lxml import etree
|
||||
|
||||
headers = {
|
||||
'Referer' :'http://music.163.com',
|
||||
'Host' :'music.163.com',
|
||||
'Accept' :'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
|
||||
'User-Agent':'Chrome/10'
|
||||
}
|
||||
|
||||
# 得到某一首歌的歌词
|
||||
def get_song_lyric(headers,lyric_url):
|
||||
res = requests.request('GET', lyric_url, headers=headers)
|
||||
if 'lrc' in res.json():
|
||||
lyric = res.json()['lrc']['lyric']
|
||||
new_lyric = re.sub(r'[\d:.[\]]','',lyric)
|
||||
return new_lyric
|
||||
else:
|
||||
return ''
|
||||
print(res.json())
|
||||
# 去掉停用词
|
||||
def remove_stop_words(f):
|
||||
stop_words = ['作词', '作曲', '编曲', 'Arranger', '录音', '混音', '人声', 'Vocal', '弦乐', 'Keyboard', '键盘', '编辑', '助理', 'Assistants', 'Mixing', 'Editing', 'Recording', '音乐', '制作', 'Producer', '发行', 'produced', 'and', 'distributed']
|
||||
for stop_word in stop_words:
|
||||
f = f.replace(stop_word, '')
|
||||
return f
|
||||
# 生成词云
|
||||
def create_word_cloud(f):
|
||||
print('根据词频,开始生成词云!')
|
||||
f = remove_stop_words(f)
|
||||
cut_text = " ".join(jieba.cut(f,cut_all=False, HMM=True))
|
||||
wc = WordCloud(
|
||||
font_path="./wc.ttf",
|
||||
max_words=100,
|
||||
width=2000,
|
||||
height=1200,
|
||||
)
|
||||
print(cut_text)
|
||||
wordcloud = wc.generate(cut_text)
|
||||
# 写词云图片
|
||||
wordcloud.to_file("wordcloud.jpg")
|
||||
# 显示词云文件
|
||||
plt.imshow(wordcloud)
|
||||
plt.axis("off")
|
||||
plt.show()
|
||||
# 得到指定歌手页面 热门前50的歌曲ID,歌曲名
|
||||
def get_songs(artist_id):
|
||||
page_url = 'https://music.163.com/artist?id=' + artist_id
|
||||
# 获取网页HTML
|
||||
res = requests.request('GET', page_url, headers=headers)
|
||||
# 用XPath解析 前50首热门歌曲
|
||||
html = etree.HTML(res.text)
|
||||
href_xpath = "//*[@id='hotsong-list']//a/@href"
|
||||
name_xpath = "//*[@id='hotsong-list']//a/text()"
|
||||
hrefs = html.xpath(href_xpath)
|
||||
names = html.xpath(name_xpath)
|
||||
# 设置热门歌曲的ID,歌曲名称
|
||||
song_ids = []
|
||||
song_names = []
|
||||
for href, name in zip(hrefs, names):
|
||||
song_ids.append(href[9:])
|
||||
song_names.append(name)
|
||||
print(href, ' ', name)
|
||||
return song_ids, song_names
|
||||
# 设置歌手ID,毛不易为12138269
|
||||
artist_id = '12138269'
|
||||
[song_ids, song_names] = get_songs(artist_id)
|
||||
# 所有歌词
|
||||
all_word = ''
|
||||
# 获取每首歌歌词
|
||||
for (song_id, song_name) in zip(song_ids, song_names):
|
||||
# 歌词API URL
|
||||
lyric_url = 'http://music.163.com/api/song/lyric?os=pc&id=' + song_id + '&lv=-1&kv=-1&tv=-1'
|
||||
lyric = get_song_lyric(headers, lyric_url)
|
||||
all_word = all_word + ' ' + lyric
|
||||
print(song_name)
|
||||
#根据词频 生成词云
|
||||
create_word_cloud(all_word)
|
||||
|
||||
```
|
||||
|
||||
运行结果:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ee/2c/ee24610141c2722660f2e4cb5824802c.png" alt=""><br>
|
||||
这个过程里有一些模块,我需要说明下。
|
||||
|
||||
首先我编写get_songs函数,可以得到指定歌手页面中热门前50的歌曲ID,歌曲名。在这个函数里,我使用requests.request函数获取毛不易歌手页面的HTML。这里需要传入指定的请求头(headers),否则获取不到完整的信息。然后用XPath解析并获取指定的内容,这个模块中,我想获取的是歌曲的链接和名称。
|
||||
|
||||
其中歌曲的链接类似 /song?id=536099160 这种形式,你能看到字符串第9位之后,就是歌曲的ID。
|
||||
|
||||
有关XPath解析的内容,我在[第10节](https://time.geekbang.org/column/article/76001)讲到过,如果你忘记了,可以看一下。一般来说,XPath解析 99%的可能都是以//开头,因为你需要获取所有符合这个XPath的内容。我们通过分析HTML代码,能看到一个关键的部分:id=‘hotsong-list’。这个代表热门歌曲列表,也正是我们想要解析的内容。我们想要获取这个热门歌曲列表下面所有的链接,XPath解析就可以写成//*[@id=‘hotsong-list’]//a。然后你能看到歌曲链接是href属性,歌曲名称是这个链接的文本。
|
||||
|
||||
获得歌曲ID之后,我们还需要知道这个歌曲的歌词,对应代码中的get_song_lyric函数,在这个函数里调用了网易云的歌词API接口,比如[http://music.163.com/api/song/lyric?os=pc&id=536099160&lv=-1&kv=-1&tv=-1](http://music.163.com/api/song/lyric?os=pc&id=536099160&lv=-1&kv=-1&tv=-1)。
|
||||
|
||||
你能看到歌词文件里面还保存了时间信息,我们需要去掉这部分。因此我使用了re.sub函数,通过正则表达式匹配,将[]中数字信息去掉,方法为re.sub(r’[\d:.[]]’,’’,lyric)。
|
||||
|
||||
最后我们还需要设置一些歌词中常用的停用词,比如“作词”“作曲”“编曲”等,编写remove_stop_words函数,将歌词文本中的停用词去掉。
|
||||
|
||||
最后编写create_word_cloud函数,通过歌词文本生成词云文件。
|
||||
|
||||
## 总结
|
||||
|
||||
今天我给你讲了词云的可视化。在这个实战中,你能看到前期的数据准备在整个过程中占了很大一部分。如果你用Python作为数据采集工具,就需要掌握Python爬虫和XPath解析,掌握这两个工具最好的方式就是多做练习。
|
||||
|
||||
我们今天讲到了词云工具WordCloud,它是一个很好用的Python工具,可以将复杂的文本通过词云图的方式呈现。需要注意的是,当我们需要使用中文字体的时候,比如黑体SimHei,就可以将WordCloud中的font_path属性设置为SimHei.ttf,你也可以设置其他艺术字体,在给毛不易的歌词做词云展示的时候,我们就用到了艺术字体。
|
||||
|
||||
你可以从GitHub上下载字体和代码:[https://github.com/cystanford/word_cloud](https://github.com/cystanford/word_cloud)。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/0c/6d/0cbc5f3e4ecd41af9a872fd9b4aed06d.png" alt=""><br>
|
||||
最后给你留一道思考题吧。我抓取了毛不易主页的歌词,是以歌手主页为粒度进行的词云可视化。实际上网易云音乐也有歌单的API,比如[http://music.163.com/api/playlist/detail?id=753776811](http://music.163.com/api/playlist/detail?id=753776811)。你能不能编写代码对歌单做个词云展示(比如歌单ID为753776811)呢?
|
||||
|
||||
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
283
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/39丨数据挖掘实战(1):信用卡违约率分析.md
Normal file
283
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/39丨数据挖掘实战(1):信用卡违约率分析.md
Normal file
@@ -0,0 +1,283 @@
|
||||
<audio id="audio" title="39丨数据挖掘实战(1):信用卡违约率分析" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a5/e3/a54f68fc3605897b0c737dd7ff0b74e3.mp3"></audio>
|
||||
|
||||
今天我来带你做一个数据挖掘的项目。在数据挖掘的过程中,我们经常会遇到一些问题,比如:如何选择各种分类器,到底选择哪个分类算法,是SVM,决策树,还是KNN?如何优化分类器的参数,以便得到更好的分类准确率?
|
||||
|
||||
这两个问题,是数据挖掘核心的问题。当然对于一个新的项目,我们还有其他的问题需要了解,比如掌握数据探索和数据可视化的方式,还需要对数据的完整性和质量做评估。这些内容我在之前的课程中都有讲到过。
|
||||
|
||||
今天的学习主要围绕下面的三个目标,并通过它们完成信用卡违约率项目的实战,这三个目标分别是:
|
||||
|
||||
<li>
|
||||
创建各种分类器,包括已经掌握的SVM、决策树、KNN分类器,以及随机森林分类器;
|
||||
</li>
|
||||
<li>
|
||||
掌握GridSearchCV工具,优化算法模型的参数;
|
||||
</li>
|
||||
<li>
|
||||
使用Pipeline管道机制进行流水线作业。因为在做分类之前,我们还需要一些准备过程,比如数据规范化,或者数据降维等。
|
||||
</li>
|
||||
|
||||
## 构建随机森林分类器
|
||||
|
||||
在算法篇中,我主要讲了数据挖掘十大经典算法。实际工作中,你也可能会用到随机森林。
|
||||
|
||||
随机森林的英文是Random Forest,英文简写是RF。它实际上是一个包含多个决策树的分类器,每一个子分类器都是一棵CART分类回归树。所以随机森林既可以做分类,又可以做回归。当它做分类的时候,输出结果是每个子分类器的分类结果中最多的那个。你可以理解是每个分类器都做投票,取投票最多的那个结果。当它做回归的时候,输出结果是每棵CART树的回归结果的平均值。
|
||||
|
||||
在sklearn中,我们使用RandomForestClassifier()构造随机森林模型,函数里有一些常用的构造参数:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/35/f9/352035fe3e92d412d652fd55c77f23f9.png" alt=""><br>
|
||||
当我们创建好之后,就可以使用fit函数拟合,使用predict函数预测。
|
||||
|
||||
## 使用GridSearchCV工具对模型参数进行调优
|
||||
|
||||
在做分类算法的时候,我们需要经常调节网络参数(对应上面的构造参数),目的是得到更好的分类结果。实际上一个分类算法有很多参数,取值范围也比较广,那么该如何调优呢?
|
||||
|
||||
Python给我们提供了一个很好用的工具GridSearchCV,它是Python的参数自动搜索模块。我们只要告诉它想要调优的参数有哪些以及参数的取值范围,它就会把所有的情况都跑一遍,然后告诉我们哪个参数是最优的,结果如何。
|
||||
|
||||
使用GridSearchCV模块需要先引用工具包,方法如下:
|
||||
|
||||
```
|
||||
from sklearn.model_selection import GridSearchCV
|
||||
|
||||
```
|
||||
|
||||
然后我们使用GridSearchCV(estimator, param_grid, cv=None, scoring=None)构造参数的自动搜索模块,这里有一些主要的参数需要说明下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/70/fd/7042cb450e5dcac9306d0178265642fd.png" alt=""><br>
|
||||
构造完GridSearchCV之后,我们就可以使用fit函数拟合训练,使用predict函数预测,这时预测采用的是最优参数情况下的分类器。
|
||||
|
||||
这里举一个简单的例子,我们用sklearn自带的IRIS数据集,采用随机森林对IRIS数据分类。假设我们想知道n_estimators在1-10的范围内取哪个值的分类结果最好,可以编写代码:
|
||||
|
||||
```
|
||||
# -*- coding: utf-8 -*-
|
||||
# 使用RandomForest对IRIS数据集进行分类
|
||||
# 利用GridSearchCV寻找最优参数
|
||||
from sklearn.ensemble import RandomForestClassifier
|
||||
from sklearn.model_selection import GridSearchCV
|
||||
from sklearn.datasets import load_iris
|
||||
rf = RandomForestClassifier()
|
||||
parameters = {"n_estimators": range(1,11)}
|
||||
iris = load_iris()
|
||||
# 使用GridSearchCV进行参数调优
|
||||
clf = GridSearchCV(estimator=rf, param_grid=parameters)
|
||||
# 对iris数据集进行分类
|
||||
clf.fit(iris.data, iris.target)
|
||||
print("最优分数: %.4lf" %clf.best_score_)
|
||||
print("最优参数:", clf.best_params_)
|
||||
运行结果如下:
|
||||
最优分数: 0.9667
|
||||
最优参数: {'n_estimators': 6}
|
||||
|
||||
```
|
||||
|
||||
你能看到当我们采用随机森林作为分类器的时候,最优准确率是0.9667,当n_estimators=6的时候,是最优参数,也就是随机森林一共有6个子决策树。
|
||||
|
||||
## 使用Pipeline管道机制进行流水线作业
|
||||
|
||||
做分类的时候往往都是有步骤的,比如先对数据进行规范化处理,你也可以用PCA方法(一种常用的降维方法)对数据降维,最后使用分类器分类。
|
||||
|
||||
Python有一种Pipeline管道机制。管道机制就是让我们把每一步都按顺序列下来,从而创建Pipeline流水线作业。每一步都采用(‘名称’, 步骤)的方式来表示。
|
||||
|
||||
我们需要先采用StandardScaler方法对数据规范化,即采用数据规范化为均值为0,方差为1的正态分布,然后采用PCA方法对数据进行降维,最后采用随机森林进行分类。
|
||||
|
||||
具体代码如下:
|
||||
|
||||
```
|
||||
from sklearn.model_selection import GridSearchCV
|
||||
pipeline = Pipeline([
|
||||
('scaler', StandardScaler()),
|
||||
('pca', PCA()),
|
||||
('randomforestclassifier', RandomForestClassifier())
|
||||
])
|
||||
|
||||
```
|
||||
|
||||
那么我们现在采用Pipeline管道机制,用随机森林对IRIS数据集做一下分类。先用StandardScaler方法对数据规范化,然后再用随机森林分类,编写代码如下:
|
||||
|
||||
```
|
||||
# -*- coding: utf-8 -*-
|
||||
# 使用RandomForest对IRIS数据集进行分类
|
||||
# 利用GridSearchCV寻找最优参数,使用Pipeline进行流水作业
|
||||
from sklearn.ensemble import RandomForestClassifier
|
||||
from sklearn.model_selection import GridSearchCV
|
||||
from sklearn.datasets import load_iris
|
||||
from sklearn.preprocessing import StandardScaler
|
||||
from sklearn.pipeline import Pipeline
|
||||
rf = RandomForestClassifier()
|
||||
parameters = {"randomforestclassifier__n_estimators": range(1,11)}
|
||||
iris = load_iris()
|
||||
pipeline = Pipeline([
|
||||
('scaler', StandardScaler()),
|
||||
('randomforestclassifier', rf)
|
||||
])
|
||||
# 使用GridSearchCV进行参数调优
|
||||
clf = GridSearchCV(estimator=pipeline, param_grid=parameters)
|
||||
# 对iris数据集进行分类
|
||||
clf.fit(iris.data, iris.target)
|
||||
print("最优分数: %.4lf" %clf.best_score_)
|
||||
print("最优参数:", clf.best_params_)
|
||||
运行结果:
|
||||
最优分数: 0.9667
|
||||
最优参数: {'randomforestclassifier__n_estimators': 9}
|
||||
|
||||
```
|
||||
|
||||
你能看到是否采用数据规范化对结果还是有一些影响的,有了GridSearchCV和Pipeline这两个工具之后,我们在使用分类器的时候就会方便很多。
|
||||
|
||||
## 对信用卡违约率进行分析
|
||||
|
||||
我们现在来做一个信用卡违约率的项目,这个数据集你可以从GitHub上下载:[https://github.com/cystanford/credit_default](https://github.com/cystanford/credit_default)。
|
||||
|
||||
这个数据集是台湾某银行2005年4月到9月的信用卡数据,数据集一共包括25个字段,具体含义如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/17/88/1730fb3a809c99950739e7f50e1a6988.jpg" alt=""><br>
|
||||
现在我们的目标是要针对这个数据集构建一个分析信用卡违约率的分类器。具体选择哪个分类器,以及分类器的参数如何优化,我们可以用GridSearchCV这个工具跑一遍。
|
||||
|
||||
先梳理下整个项目的流程:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/92/a5/929c96584cbc25972f63ef39101c96a5.jpg" alt="">
|
||||
|
||||
<li>
|
||||
加载数据;
|
||||
</li>
|
||||
<li>
|
||||
准备阶段:探索数据,采用数据可视化方式可以让我们对数据有更直观的了解,比如我们想要了解信用卡违约率和不违约率的人数。因为数据集没有专门的测试集,我们还需要使用train_test_split划分数据集。
|
||||
</li>
|
||||
<li>
|
||||
分类阶段:之所以把数据规范化放到这个阶段,是因为我们可以使用Pipeline管道机制,将数据规范化设置为第一步,分类为第二步。因为我们不知道采用哪个分类器效果好,所以我们需要多用几个分类器,比如SVM、决策树、随机森林和KNN。然后通过GridSearchCV工具,找到每个分类器的最优参数和最优分数,最终找到最适合这个项目的分类器和该分类器的参数。
|
||||
</li>
|
||||
|
||||
基于上面的流程,具体代码如下:
|
||||
|
||||
```
|
||||
# -*- coding: utf-8 -*-
|
||||
# 信用卡违约率分析
|
||||
import pandas as pd
|
||||
from sklearn.model_selection import learning_curve, train_test_split,GridSearchCV
|
||||
from sklearn.preprocessing import StandardScaler
|
||||
from sklearn.pipeline import Pipeline
|
||||
from sklearn.metrics import accuracy_score
|
||||
from sklearn.svm import SVC
|
||||
from sklearn.tree import DecisionTreeClassifier
|
||||
from sklearn.ensemble import RandomForestClassifier
|
||||
from sklearn.neighbors import KNeighborsClassifier
|
||||
from matplotlib import pyplot as plt
|
||||
import seaborn as sns
|
||||
# 数据加载
|
||||
data = data = pd.read_csv('./UCI_Credit_Card.csv')
|
||||
# 数据探索
|
||||
print(data.shape) # 查看数据集大小
|
||||
print(data.describe()) # 数据集概览
|
||||
# 查看下一个月违约率的情况
|
||||
next_month = data['default.payment.next.month'].value_counts()
|
||||
print(next_month)
|
||||
df = pd.DataFrame({'default.payment.next.month': next_month.index,'values': next_month.values})
|
||||
plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
|
||||
plt.figure(figsize = (6,6))
|
||||
plt.title('信用卡违约率客户\n (违约:1,守约:0)')
|
||||
sns.set_color_codes("pastel")
|
||||
sns.barplot(x = 'default.payment.next.month', y="values", data=df)
|
||||
locs, labels = plt.xticks()
|
||||
plt.show()
|
||||
# 特征选择,去掉ID字段、最后一个结果字段即可
|
||||
data.drop(['ID'], inplace=True, axis =1) #ID这个字段没有用
|
||||
target = data['default.payment.next.month'].values
|
||||
columns = data.columns.tolist()
|
||||
columns.remove('default.payment.next.month')
|
||||
features = data[columns].values
|
||||
# 30%作为测试集,其余作为训练集
|
||||
train_x, test_x, train_y, test_y = train_test_split(features, target, test_size=0.30, stratify = target, random_state = 1)
|
||||
|
||||
# 构造各种分类器
|
||||
classifiers = [
|
||||
SVC(random_state = 1, kernel = 'rbf'),
|
||||
DecisionTreeClassifier(random_state = 1, criterion = 'gini'),
|
||||
RandomForestClassifier(random_state = 1, criterion = 'gini'),
|
||||
KNeighborsClassifier(metric = 'minkowski'),
|
||||
]
|
||||
# 分类器名称
|
||||
classifier_names = [
|
||||
'svc',
|
||||
'decisiontreeclassifier',
|
||||
'randomforestclassifier',
|
||||
'kneighborsclassifier',
|
||||
]
|
||||
# 分类器参数
|
||||
classifier_param_grid = [
|
||||
{'svc__C':[1], 'svc__gamma':[0.01]},
|
||||
{'decisiontreeclassifier__max_depth':[6,9,11]},
|
||||
{'randomforestclassifier__n_estimators':[3,5,6]} ,
|
||||
{'kneighborsclassifier__n_neighbors':[4,6,8]},
|
||||
]
|
||||
|
||||
# 对具体的分类器进行GridSearchCV参数调优
|
||||
def GridSearchCV_work(pipeline, train_x, train_y, test_x, test_y, param_grid, score = 'accuracy'):
|
||||
response = {}
|
||||
gridsearch = GridSearchCV(estimator = pipeline, param_grid = param_grid, scoring = score)
|
||||
# 寻找最优的参数 和最优的准确率分数
|
||||
search = gridsearch.fit(train_x, train_y)
|
||||
print("GridSearch最优参数:", search.best_params_)
|
||||
print("GridSearch最优分数: %0.4lf" %search.best_score_)
|
||||
predict_y = gridsearch.predict(test_x)
|
||||
print("准确率 %0.4lf" %accuracy_score(test_y, predict_y))
|
||||
response['predict_y'] = predict_y
|
||||
response['accuracy_score'] = accuracy_score(test_y,predict_y)
|
||||
return response
|
||||
|
||||
for model, model_name, model_param_grid in zip(classifiers, classifier_names, classifier_param_grid):
|
||||
pipeline = Pipeline([
|
||||
('scaler', StandardScaler()),
|
||||
(model_name, model)
|
||||
])
|
||||
result = GridSearchCV_work(pipeline, train_x, train_y, test_x, test_y, model_param_grid , score = 'accuracy')
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
运行结果:
|
||||
(30000, 25)
|
||||
ID ... default.payment.next.month
|
||||
count 30000.000000 ... 30000.000000
|
||||
mean 15000.500000 ... 0.221200
|
||||
std 8660.398374 ... 0.415062
|
||||
min 1.000000 ... 0.000000
|
||||
25% 7500.750000 ... 0.000000
|
||||
50% 15000.500000 ... 0.000000
|
||||
75% 22500.250000 ... 0.000000
|
||||
max 30000.000000 ... 1.000000
|
||||
|
||||
[8 rows x 25 columns]
|
||||
|
||||
GridSearch最优参数: {'svc__C': 1, 'svc__gamma': 0.01}
|
||||
GridSearch最优分数: 0.8174
|
||||
准确率 0.8172
|
||||
GridSearch最优参数: {'decisiontreeclassifier__max_depth': 6}
|
||||
GridSearch最优分数: 0.8186
|
||||
准确率 0.8113
|
||||
GridSearch最优参数: {'randomforestclassifier__n_estimators': 6}
|
||||
GridSearch最优分数: 0.7998
|
||||
准确率 0.7994
|
||||
GridSearch最优参数: {'kneighborsclassifier__n_neighbors': 8}
|
||||
GridSearch最优分数: 0.8040
|
||||
准确率 0.8036
|
||||
|
||||
```
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/18/72/187d0233d4fb5f07a9653e5ae4754372.png" alt=""><br>
|
||||
从结果中,我们能看到SVM分类器的准确率最高,测试准确率为0.8172。
|
||||
|
||||
在决策树分类中,我设置了3种最大深度,当最大深度=6时结果最优,测试准确率为0.8113;在随机森林分类中,我设置了3个决策树个数的取值,取值为6时结果最优,测试准确率为0.7994;在KNN分类中,我设置了3个n的取值,取值为8时结果最优,测试准确率为0.8036。
|
||||
|
||||
## 总结
|
||||
|
||||
今天我给你讲了随机森林的概念及工具的使用,另外针对数据挖掘算法中经常采用的参数调优,也介绍了GridSearchCV工具这个利器。并将这两者结合起来,在信用卡违约分析这个项目中进行了使用。
|
||||
|
||||
很多时候,我们不知道该采用哪种分类算法更适合。即便是对于一种分类算法,也有很多参数可以调优,每个参数都有一定的取值范围。我们可以把想要采用的分类器,以及这些参数的取值范围都设置到数组里,然后使用GridSearchCV工具进行调优。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/14/16/14f9cddc17d6cceb0b8cbc4381c65216.png" alt=""><br>
|
||||
今天我们讲了如何使用GridSearchCV做参数调优,你可以说说你的理解,如果有使用的经验也可以分享下。
|
||||
|
||||
另外针对信用卡违约率分析这个项目,我们使用了SVM、决策树、随机森林和KNN分类器,你能不能编写代码使用AdaBoost分类器做分类呢?其中n_estimators的取值有10、50、100三种可能,你可以使用GridSearchCV运行看看最优参数是多少,测试准确率是多少?
|
||||
|
||||
欢迎你在评论区与我分享你的答案,如果有问题也可以写在评论区。如果你觉得这篇文章有价值,欢迎把它分享给你的朋友或者同事。
|
||||
|
||||
|
||||
291
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/40丨数据挖掘实战(2):信用卡诈骗分析.md
Normal file
291
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/40丨数据挖掘实战(2):信用卡诈骗分析.md
Normal file
@@ -0,0 +1,291 @@
|
||||
<audio id="audio" title="40丨数据挖掘实战(2):信用卡诈骗分析" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/8c/28/8c087cde91c03ca9bb6d1f23d7b65a28.mp3"></audio>
|
||||
|
||||
上一篇文章中,我们用随机森林以及之前讲过的SVM、决策树和KNN分类器对信用卡违约数据进行了分析,这节课我们来研究下信用卡欺诈。
|
||||
|
||||
相比于信用卡违约的比例,信用卡欺诈的比例更小,但是危害极大。如何通过以往的交易数据分析出每笔交易是否正常,是否存在盗刷风险是我们这次项目的目标。
|
||||
|
||||
通过今天的学习,你需要掌握以下几个方面:
|
||||
|
||||
<li>
|
||||
了解逻辑回归分类,以及如何在sklearn中使用它;
|
||||
</li>
|
||||
<li>
|
||||
信用卡欺诈属于二分类问题,欺诈交易在所有交易中的比例很小,对于这种数据不平衡的情况,到底采用什么样的模型评估标准会更准确;
|
||||
</li>
|
||||
<li>
|
||||
完成信用卡欺诈分析的实战项目,并通过数据可视化对数据探索和模型结果评估进一步加强了解。
|
||||
</li>
|
||||
|
||||
## 构建逻辑回归分类器
|
||||
|
||||
逻辑回归虽然不在我们讲解的十大经典数据挖掘算法里面,但也是常用的数据挖掘算法。
|
||||
|
||||
逻辑回归,也叫作logistic回归。虽然名字中带有“回归”,但它实际上是分类方法,主要解决的是二分类问题,当然它也可以解决多分类问题,只是二分类更常见一些。
|
||||
|
||||
在逻辑回归中使用了Logistic函数,也称为Sigmoid函数。Sigmoid函数是在深度学习中经常用到的函数之一,函数公式为:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/3e/18/3e7c7cb4d26d1a71f958610f26d20818.png" alt=""><br>
|
||||
函数的图形如下所示,类似S状:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b7/3b/b7a5d39d91fda02b21669137a489743b.png" alt=""><br>
|
||||
你能看出g(z)的结果在0-1之间,当z越大的时候,g(z)越大,当z趋近于无穷大的时候,g(z)趋近于1。同样当z趋近于无穷小的时候,g(z)趋近于0。同时,函数值以0.5为中心。
|
||||
|
||||
为什么逻辑回归算法是基于Sigmoid函数实现的呢?你可以这样理解:我们要实现一个二分类任务,0即为不发生,1即为发生。我们给定一些历史数据X和y。其中X代表样本的n个特征,y代表正例和负例,也就是0或1的取值。通过历史样本的学习,我们可以得到一个模型,当给定新的X的时候,可以预测出y。这里我们得到的y是一个预测的概率,通常不是0%和100%,而是中间的取值,那么我们就可以认为概率大于50%的时候,即为发生(正例),概率小于50%的时候,即为不发生(负例)。这样就完成了二分类的预测。
|
||||
|
||||
逻辑回归模型的求解这里不做介绍,我们来看下如何使用sklearn中的逻辑回归工具。在sklearn中,我们使用LogisticRegression()函数构建逻辑回归分类器,函数里有一些常用的构造参数:
|
||||
|
||||
<li>
|
||||
penalty:惩罚项,取值为l1或l2,默认为l2。当模型参数满足高斯分布的时候,使用l2,当模型参数满足拉普拉斯分布的时候,使用l1;
|
||||
</li>
|
||||
<li>
|
||||
solver:代表的是逻辑回归损失函数的优化方法。有5个参数可选,分别为liblinear、lbfgs、newton-cg、sag和saga。默认为liblinear,适用于数据量小的数据集,当数据量大的时候可以选用sag或saga方法。
|
||||
</li>
|
||||
<li>
|
||||
max_iter:算法收敛的最大迭代次数,默认为10。
|
||||
</li>
|
||||
<li>
|
||||
n_jobs:拟合和预测的时候CPU的核数,默认是1,也可以是整数,如果是-1则代表CPU的核数。
|
||||
</li>
|
||||
|
||||
当我们创建好之后,就可以使用fit函数拟合,使用predict函数预测。
|
||||
|
||||
## 模型评估指标
|
||||
|
||||
我们之前对模型做评估时,通常采用的是准确率(accuracy),它指的是分类器正确分类的样本数与总体样本数之间的比例。这个指标对大部分的分类情况是有效的,不过当分类结果严重不平衡的时候,准确率很难反应模型的好坏。
|
||||
|
||||
举个例子,对于机场安检中恐怖分子的判断,就不能采用准确率对模型进行评估。我们知道恐怖分子的比例是极低的,因此当我们用准确率做判断时,如果准确率高达99.999%,就说明这个模型一定好么?
|
||||
|
||||
其实正因为现实生活中恐怖分子的比例极低,就算我们不能识别出一个恐怖分子,也会得到非常高的准确率。因为准确率的评判标准是正确分类的样本个数与总样本数之间的比例。因此非恐怖分子的比例会很高,就算我们识别不出来恐怖分子,正确分类的个数占总样本的比例也会很高,也就是准确率高。
|
||||
|
||||
实际上我们应该更关注恐怖分子的识别,这里先介绍下数据预测的四种情况:TP、FP、TN、FN。我们用第二个字母P或N代表预测为正例还是负例,P为正,N为负。第一个字母T或F代表的是预测结果是否正确,T为正确,F为错误。
|
||||
|
||||
所以四种情况分别为:
|
||||
|
||||
<li>
|
||||
TP:预测为正,判断正确;
|
||||
</li>
|
||||
<li>
|
||||
FP:预测为正,判断错误;
|
||||
</li>
|
||||
<li>
|
||||
TN:预测为负,判断正确;
|
||||
</li>
|
||||
<li>
|
||||
FN:预测为负,判断错误。
|
||||
</li>
|
||||
|
||||
我们知道样本总数=TP+FP+TN+FN,预测正确的样本数为TP+TN,因此准确率Accuracy = (TP+TN)/(TP+TN+FN+FP)。
|
||||
|
||||
实际上,对于分类不平衡的情况,有两个指标非常重要,它们分别是精确度和召回率。
|
||||
|
||||
精确率 P = TP/ (TP+FP),对应上面恐怖分子这个例子,在所有判断为恐怖分子的人数中,真正是恐怖分子的比例。
|
||||
|
||||
召回率 R = TP/ (TP+FN),也称为查全率。代表的是恐怖分子被正确识别出来的个数与恐怖分子总数的比例。
|
||||
|
||||
为什么要统计召回率和精确率这两个指标呢?假设我们只统计召回率,当召回率等于100%的时候,模型是否真的好呢?
|
||||
|
||||
举个例子,假设我们把机场所有的人都认为是恐怖分子,恐怖分子都会被正确识别,这个数字与恐怖分子的总数比例等于100%,但是这个结果是没有意义的。如果我们认为机场里所有人都是恐怖分子的话,那么非恐怖分子(极高比例)都会认为是恐怖分子,误判率太高了,所以我们还需要统计精确率作为召回率的补充。
|
||||
|
||||
实际上有一个指标综合了精确率和召回率,可以更好地评估模型的好坏。这个指标叫做F1,用公式表示为:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b1/ce/b122244eae9a74eded619d14c0bc12ce.png" alt=""><br>
|
||||
F1作为精确率P和召回率R的调和平均,数值越大代表模型的结果越好。
|
||||
|
||||
## 对信用卡违约率进行分析
|
||||
|
||||
我们来看一个信用卡欺诈分析的项目,这个数据集你可以从百度网盘(因为数据集大于100M,所以采用了网盘)中下载:
|
||||
|
||||
>
|
||||
链接:[https://pan.baidu.com/s/14F8WuX0ZJntdB_r1EC08HA](https://pan.baidu.com/s/14F8WuX0ZJntdB_r1EC08HA) 提取码:58gp
|
||||
|
||||
|
||||
数据集包括了2013年9月份两天时间内的信用卡交易数据,284807笔交易中,一共有492笔是欺诈行为。输入数据一共包括了28个特征V1,V2,……V28对应的取值,以及交易时间Time和交易金额Amount。为了保护数据隐私,我们不知道V1到V28这些特征代表的具体含义,只知道这28个特征值是通过PCA变换得到的结果。另外字段Class代表该笔交易的分类,Class=0为正常(非欺诈),Class=1代表欺诈。
|
||||
|
||||
我们的目标是针对这个数据集构建一个信用卡欺诈分析的分类器,采用的是逻辑回归。从数据中你能看到欺诈行为只占到了492/284807=0.172%,数据分类结果的分布是非常不平衡的,因此我们不能使用准确率评估模型的好坏,而是需要统计F1值(综合精确率和召回率)。我们先梳理下整个项目的流程:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/92/a5/929c96584cbc25972f63ef39101c96a5.jpg" alt="">
|
||||
|
||||
<li>
|
||||
加载数据;
|
||||
</li>
|
||||
<li>
|
||||
准备阶段:我们需要探索数据,用数据可视化的方式查看分类结果的情况,以及随着时间的变化,欺诈交易和正常交易的分布情况。上面已经提到过,V1-V28的特征值都经过PCA的变换,但是其余的两个字段,Time和Amount还需要进行规范化。Time字段和交易本身是否为欺诈交易无关,因此我们不作为特征选择,只需要对Amount做数据规范化就行了。同时数据集没有专门的测试集,使用train_test_split对数据集进行划分;
|
||||
</li>
|
||||
<li>
|
||||
分类阶段:我们需要创建逻辑回归分类器,然后传入训练集数据进行训练,并传入测试集预测结果,将预测结果与测试集的结果进行比对。这里的模型评估指标用到了精确率、召回率和F1值。同时我们将精确率-召回率进行了可视化呈现。
|
||||
</li>
|
||||
|
||||
基于上面的流程,具体代码如下:
|
||||
|
||||
```
|
||||
# -*- coding:utf-8 -*-
|
||||
# 使用逻辑回归对信用卡欺诈进行分类
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import seaborn as sns
|
||||
import matplotlib.pyplot as plt
|
||||
import itertools
|
||||
from sklearn.linear_model import LogisticRegression
|
||||
from sklearn.model_selection import train_test_split
|
||||
from sklearn.metrics import confusion_matrix, precision_recall_curve
|
||||
from sklearn.preprocessing import StandardScaler
|
||||
import warnings
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
# 混淆矩阵可视化
|
||||
def plot_confusion_matrix(cm, classes, normalize = False, title = 'Confusion matrix"', cmap = plt.cm.Blues) :
|
||||
plt.figure()
|
||||
plt.imshow(cm, interpolation = 'nearest', cmap = cmap)
|
||||
plt.title(title)
|
||||
plt.colorbar()
|
||||
tick_marks = np.arange(len(classes))
|
||||
plt.xticks(tick_marks, classes, rotation = 0)
|
||||
plt.yticks(tick_marks, classes)
|
||||
|
||||
thresh = cm.max() / 2.
|
||||
for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])) :
|
||||
plt.text(j, i, cm[i, j],
|
||||
horizontalalignment = 'center',
|
||||
color = 'white' if cm[i, j] > thresh else 'black')
|
||||
|
||||
plt.tight_layout()
|
||||
plt.ylabel('True label')
|
||||
plt.xlabel('Predicted label')
|
||||
plt.show()
|
||||
|
||||
# 显示模型评估结果
|
||||
def show_metrics():
|
||||
tp = cm[1,1]
|
||||
fn = cm[1,0]
|
||||
fp = cm[0,1]
|
||||
tn = cm[0,0]
|
||||
print('精确率: {:.3f}'.format(tp/(tp+fp)))
|
||||
print('召回率: {:.3f}'.format(tp/(tp+fn)))
|
||||
print('F1值: {:.3f}'.format(2*(((tp/(tp+fp))*(tp/(tp+fn)))/((tp/(tp+fp))+(tp/(tp+fn))))))
|
||||
# 绘制精确率-召回率曲线
|
||||
def plot_precision_recall():
|
||||
plt.step(recall, precision, color = 'b', alpha = 0.2, where = 'post')
|
||||
plt.fill_between(recall, precision, step ='post', alpha = 0.2, color = 'b')
|
||||
plt.plot(recall, precision, linewidth=2)
|
||||
plt.xlim([0.0,1])
|
||||
plt.ylim([0.0,1.05])
|
||||
plt.xlabel('召回率')
|
||||
plt.ylabel('精确率')
|
||||
plt.title('精确率-召回率 曲线')
|
||||
plt.show();
|
||||
|
||||
# 数据加载
|
||||
data = pd.read_csv('./creditcard.csv')
|
||||
# 数据探索
|
||||
print(data.describe())
|
||||
# 设置plt正确显示中文
|
||||
plt.rcParams['font.sans-serif'] = ['SimHei']
|
||||
# 绘制类别分布
|
||||
plt.figure()
|
||||
ax = sns.countplot(x = 'Class', data = data)
|
||||
plt.title('类别分布')
|
||||
plt.show()
|
||||
# 显示交易笔数,欺诈交易笔数
|
||||
num = len(data)
|
||||
num_fraud = len(data[data['Class']==1])
|
||||
print('总交易笔数: ', num)
|
||||
print('诈骗交易笔数:', num_fraud)
|
||||
print('诈骗交易比例:{:.6f}'.format(num_fraud/num))
|
||||
# 欺诈和正常交易可视化
|
||||
f, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=(15,8))
|
||||
bins = 50
|
||||
ax1.hist(data.Time[data.Class == 1], bins = bins, color = 'deeppink')
|
||||
ax1.set_title('诈骗交易')
|
||||
ax2.hist(data.Time[data.Class == 0], bins = bins, color = 'deepskyblue')
|
||||
ax2.set_title('正常交易')
|
||||
plt.xlabel('时间')
|
||||
plt.ylabel('交易次数')
|
||||
plt.show()
|
||||
# 对Amount进行数据规范化
|
||||
data['Amount_Norm'] = StandardScaler().fit_transform(data['Amount'].values.reshape(-1,1))
|
||||
# 特征选择
|
||||
y = np.array(data.Class.tolist())
|
||||
data = data.drop(['Time','Amount','Class'],axis=1)
|
||||
X = np.array(data.as_matrix())
|
||||
# 准备训练集和测试集
|
||||
train_x, test_x, train_y, test_y = train_test_split (X, y, test_size = 0.1, random_state = 33)
|
||||
|
||||
# 逻辑回归分类
|
||||
clf = LogisticRegression()
|
||||
clf.fit(train_x, train_y)
|
||||
predict_y = clf.predict(test_x)
|
||||
# 预测样本的置信分数
|
||||
score_y = clf.decision_function(test_x)
|
||||
# 计算混淆矩阵,并显示
|
||||
cm = confusion_matrix(test_y, predict_y)
|
||||
class_names = [0,1]
|
||||
# 显示混淆矩阵
|
||||
plot_confusion_matrix(cm, classes = class_names, title = '逻辑回归 混淆矩阵')
|
||||
# 显示模型评估分数
|
||||
show_metrics()
|
||||
# 计算精确率,召回率,阈值用于可视化
|
||||
precision, recall, thresholds = precision_recall_curve(test_y, score_y)
|
||||
plot_precision_recall()
|
||||
|
||||
|
||||
```
|
||||
|
||||
运行结果:
|
||||
|
||||
```
|
||||
Time V1 ... Amount Class
|
||||
count 284807.000000 2.848070e+05 ... 284807.000000 284807.000000
|
||||
mean 94813.859575 1.165980e-15 ... 88.349619 0.001727
|
||||
std 47488.145955 1.958696e+00 ... 250.120109 0.041527
|
||||
min 0.000000 -5.640751e+01 ... 0.000000 0.000000
|
||||
25% 54201.500000 -9.203734e-01 ... 5.600000 0.000000
|
||||
50% 84692.000000 1.810880e-02 ... 22.000000 0.000000
|
||||
75% 139320.500000 1.315642e+00 ... 77.165000 0.000000
|
||||
max 172792.000000 2.454930e+00 ... 25691.160000 1.000000
|
||||
|
||||
[8 rows x 31 columns]
|
||||
|
||||
|
||||
```
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/5e/61/5e98974d6c2e87168b40e5f751d00f61.png" alt="">
|
||||
|
||||
```
|
||||
总交易笔数: 284807
|
||||
诈骗交易笔数: 492
|
||||
诈骗交易比例:0.001727
|
||||
|
||||
```
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c8/d2/c8a59cb4f3d94c91eb6648be1b0429d2.png" alt="">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/bf/39/bfe65c34b74de661477d9b59d4db6a39.png" alt="">
|
||||
|
||||
```
|
||||
精确率: 0.841
|
||||
召回率: 0.617
|
||||
F1值: 0.712
|
||||
|
||||
```
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/28/69/28ccd0f8d609046b2bafb27fb1195269.png" alt=""><br>
|
||||
你能看出来欺诈交易的笔数为492笔,占所有交易的比例是很低的,即0.001727,我们可以通过数据可视化的方式对欺诈交易和正常交易的分布进行呈现。另外通过可视化,我们也能看出精确率和召回率之间的关系,当精确率高的时候,召回率往往很低,召回率高的时候,精确率会比较低。
|
||||
|
||||
代码有一些模块需要说明下。
|
||||
|
||||
我定义了plot_confusion_matrix函数对混淆矩阵进行可视化。什么是混淆矩阵呢?混淆矩阵也叫误差矩阵,实际上它就是TP、FP、TN、FN这四个数值的矩阵表示,帮助我们判断预测值和实际值相比,对了多少。从这个例子中,你能看出TP=37,FP=7,FN=23。所以精确率P=TP/(TP+FP)=37/(37+7)=0.841,召回率R=TP/(TP+FN)=37/(37+23)=0.617。
|
||||
|
||||
然后使用了sklearn中的precision_recall_curve函数,通过预测值和真实值来计算精确率-召回率曲线。precision_recall_curve函数会计算在不同概率阈值情况下的精确率和召回率。最后定义plot_precision_recall函数,绘制曲线。
|
||||
|
||||
## 总结
|
||||
|
||||
今天我给你讲了逻辑回归的概念和相关工具的使用,另外学习了在数据样本不平衡的情况下,如何评估模型。这里你需要了解精确率,召回率和F1的概念和计算方式。最后在信用卡欺诈分析的项目中,我们使用了逻辑回归工具,并对混淆矩阵进行了计算,同时在模型结果评估中,使用了精确率、召回率和F1值,最后得到精确率-召回率曲线的可视化结果。
|
||||
|
||||
从这个项目中你能看出来,不是所有的分类都是样本平衡的情况,针对正例比例极低的情况,比如信用卡欺诈、某些疾病的识别,或者是恐怖分子的判断等,都需要采用精确率-召回率来进行统计。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ab/50/abee1a58b99814f1e0218778b98a6950.png" alt=""><br>
|
||||
最后留两道思考题吧,今天我们对信用卡欺诈数据集进行了分析,它是一个不平衡数据集,你知道还有哪些数据属于不平衡数据么?另外,请你使用线性SVM(对应sklearn中的LinearSVC)对信用卡欺诈数据集进行分类,并计算精确率、召回率和F1值。
|
||||
|
||||
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
267
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/41丨数据挖掘实战(3):如何对比特币走势进行预测?.md
Normal file
267
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/41丨数据挖掘实战(3):如何对比特币走势进行预测?.md
Normal file
@@ -0,0 +1,267 @@
|
||||
<audio id="audio" title="41丨数据挖掘实战(3):如何对比特币走势进行预测?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f6/ac/f6b339e9a4cd7c4e8ecc35fd547439ac.mp3"></audio>
|
||||
|
||||
今天我带你用数据挖掘对比特币的走势进行预测和分析。
|
||||
|
||||
我们之前介绍了数据挖掘算法中的分类、聚类、回归和关联分析算法,那么对于比特币走势的预测,采用哪种方法比较好呢?
|
||||
|
||||
可能有些人会认为采用回归分析会好一些,因为预测的结果是连续的数值类型。实际上,数据挖掘算法还有一种叫时间序列分析的算法,时间序列分析模型建立了观察结果与时间变化的关系,能帮我们预测未来一段时间内的结果变化情况。
|
||||
|
||||
那么时间序列分析和回归分析有哪些区别呢?
|
||||
|
||||
首先,在选择模型前,我们需要确定结果与变量之间的关系。回归分析训练得到的是目标变量y与自变量x(一个或多个)的相关性,然后通过新的自变量x来预测目标变量y。而时间序列分析得到的是目标变量y与时间的相关性。
|
||||
|
||||
另外,回归分析擅长的是多变量与目标结果之间的分析,即便是单一变量,也往往与时间无关。而时间序列分析建立在时间变化的基础上,它会分析目标变量的趋势、周期、时期和不稳定因素等。这些趋势和周期都是在时间维度的基础上,我们要观察的重要特征。
|
||||
|
||||
那么针对今天要进行的预测比特币走势的项目,我们都需要掌握哪些目标呢?
|
||||
|
||||
<li>
|
||||
了解时间序列预测的概念,以及常用的模型算法,包括AR、MA、ARMA、ARIMA模型等;
|
||||
</li>
|
||||
<li>
|
||||
掌握并使用ARMA模型工具,对一个时间序列数据进行建模和预测;
|
||||
</li>
|
||||
<li>
|
||||
对比特币的历史数据进行时间序列建模,并预测未来6个月的走势。
|
||||
</li>
|
||||
|
||||
## 时间序列预测
|
||||
|
||||
关于时间序列,你可以把它理解为按照时间顺序组成的数字序列。实际上在中国古代的农业社会中,人们就将一年中不同时间节点和天气的规律总结了下来,形成了二十四节气,也就是从时间序列中观察天气和太阳的规律(只是当时没有时间序列模型和相应工具),从而使得农业得到迅速发展。在现代社会,时间序列在金融、经济、商业领域拥有广泛的应用。
|
||||
|
||||
在时间序列预测模型中,有一些经典的模型,包括AR、MA、ARMA、ARIMA。我来给你简单介绍一下。
|
||||
|
||||
AR的英文全称叫做Auto Regressive,中文叫自回归模型。这个算法的思想比较简单,它认为过去若干时刻的点通过线性组合,再加上白噪声就可以预测未来某个时刻的点。
|
||||
|
||||
在我们日常生活环境中就存在白噪声,在数据挖掘的过程中,你可以把它理解为一个期望为0,方差为常数的纯随机过程。AR模型还存在一个阶数,称为AR(p)模型,也叫作p阶自回归模型。它指的是通过这个时刻点的前p个点,通过线性组合再加上白噪声来预测当前时刻点的值。
|
||||
|
||||
MA的英文全称叫做 Moving Average,中文叫做滑动平均模型。它与AR模型大同小异,AR模型是历史时序值的线性组合,MA是通过历史白噪声进行线性组合来影响当前时刻点。AR模型中的历史白噪声是通过影响历史时序值,从而间接影响到当前时刻点的预测值。同样MA模型也存在一个阶数,称为MA(q)模型,也叫作q阶移动平均模型。我们能看到AR和MA模型都存在阶数,在AR模型中,我们用p表示,在MA模型中我们用q表示,这两个模型大同小异,与AR模型不同的是MA模型是历史白噪声的线性组合。
|
||||
|
||||
ARMA的英文全称是Auto Regressive Moving Average,中文叫做自回归滑动平均模型,也就是AR模型和MA模型的混合。相比AR模型和MA模型,它有更准确的估计。同样ARMA模型存在p和q两个阶数,称为ARMA(p,q)模型。
|
||||
|
||||
ARIMA的英文全称是Auto Regressive Integrated Moving Average模型,中文叫差分自回归滑动平均模型,也叫求合自回归滑动平均模型。相比于ARMA,ARIMA多了一个差分的过程,作用是对不平稳数据进行差分平稳,在差分平稳后再进行建模。ARIMA的原理和ARMA模型一样。相比于ARMA(p,q)的两个阶数,ARIMA是一个三元组的阶数(p,d,q),称为ARIMA(p,d,q)模型。其中d是差分阶数。
|
||||
|
||||
## ARMA模型工具
|
||||
|
||||
上面介绍的AR,MA,ARMA,ARIMA四种模型,你只需要了解基础概念即可,中间涉及到的一些数学公式这里不进行展开。
|
||||
|
||||
在实际工作中,我们更多的是使用工具,我在这里主要讲解下如何使用ARMA模型工具。
|
||||
|
||||
在使用ARMA工具前,你需要先引用相关工具包:
|
||||
|
||||
```
|
||||
from statsmodels.tsa.arima_model import ARMA
|
||||
|
||||
```
|
||||
|
||||
然后通过ARMA(endog,order,exog=None)创建ARMA类,这里有一些主要的参数简单说明下:
|
||||
|
||||
endog:英文是endogenous variable,代表内生变量,又叫非政策性变量,它是由模型决定的,不被政策左右,可以说是我们想要分析的变量,或者说是我们这次项目中需要用到的变量。
|
||||
|
||||
order:代表是p和q的值,也就是ARMA中的阶数。
|
||||
|
||||
exog:英文是exogenous variables,代表外生变量。外生变量和内生变量一样是经济模型中的两个重要变量。相对于内生变量而言,外生变量又称作为政策性变量,在经济机制内受外部因素的影响,不是我们模型要研究的变量。
|
||||
|
||||
举个例子,如果我们想要创建ARMA(7,0)模型,可以写成:ARMA(data,(7,0)),其中data是我们想要观察的变量,(7,0)代表(p,q)的阶数。
|
||||
|
||||
创建好之后,我们可以通过fit函数进行拟合,通过predict(start, end)函数进行预测,其中start为预测的起始时间,end为预测的终止时间。
|
||||
|
||||
下面我们使用ARMA模型对一组时间序列做建模,代码如下:
|
||||
|
||||
```
|
||||
# coding:utf-8
|
||||
# 用ARMA进行时间序列预测
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import statsmodels.api as sm
|
||||
from statsmodels.tsa.arima_model import ARMA
|
||||
from statsmodels.graphics.api import qqplot
|
||||
# 创建数据
|
||||
data = [5922, 5308, 5546, 5975, 2704, 1767, 4111, 5542, 4726, 5866, 6183, 3199, 1471, 1325, 6618, 6644, 5337, 7064, 2912, 1456, 4705, 4579, 4990, 4331, 4481, 1813, 1258, 4383, 5451, 5169, 5362, 6259, 3743, 2268, 5397, 5821, 6115, 6631, 6474, 4134, 2728, 5753, 7130, 7860, 6991, 7499, 5301, 2808, 6755, 6658, 7644, 6472, 8680, 6366, 5252, 8223, 8181, 10548, 11823, 14640, 9873, 6613, 14415, 13204, 14982, 9690, 10693, 8276, 4519, 7865, 8137, 10022, 7646, 8749, 5246, 4736, 9705, 7501, 9587, 10078, 9732, 6986, 4385, 8451, 9815, 10894, 10287, 9666, 6072, 5418]
|
||||
data=pd.Series(data)
|
||||
data_index = sm.tsa.datetools.dates_from_range('1901','1990')
|
||||
# 绘制数据图
|
||||
data.index = pd.Index(data_index)
|
||||
data.plot(figsize=(12,8))
|
||||
plt.show()
|
||||
# 创建ARMA模型# 创建ARMA模型
|
||||
arma = ARMA(data,(7,0)).fit()
|
||||
print('AIC: %0.4lf' %arma.aic)
|
||||
# 模型预测
|
||||
predict_y = arma.predict('1990', '2000')
|
||||
# 预测结果绘制
|
||||
fig, ax = plt.subplots(figsize=(12, 8))
|
||||
ax = data.ix['1901':].plot(ax=ax)
|
||||
predict_y.plot(ax=ax)
|
||||
plt.show()
|
||||
|
||||
```
|
||||
|
||||
运行结果:
|
||||
|
||||
```
|
||||
AIC: 1619.6323
|
||||
|
||||
```
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/71/6a/71c5fe77926ba65e4b4aca48c337c66a.png" alt="">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/08/fc/082ff6d7e85e176b8fd5c38f528314fc.png" alt=""><br>
|
||||
我创建了1901年-1990年之间的时间序列数据data,然后创建ARMA(7,0)模型,并传入时间序列数据data,使用fit函数拟合,然后对1990年-2000年之间的数据进行预测,最后绘制预测结果。
|
||||
|
||||
你能看到ARMA工具的使用还是很方便的,只是我们需要p和q的取值。实际项目中,我们可以给p和q指定一个范围,让ARMA都运行一下,然后选择最适合的模型。
|
||||
|
||||
你可能会问,怎么判断一个模型是否适合?
|
||||
|
||||
我们需要引入AIC准则,也叫作赤池消息准则,它是衡量统计模型拟合好坏的一个标准,数值越小代表模型拟合得越好。
|
||||
|
||||
在这个例子中,你能看到ARMA(7,0)这个模型拟合出来的AIC是1619.6323(并不一定是最优)。
|
||||
|
||||
## 对比特币走势进行预测
|
||||
|
||||
我们都知道比特币的走势除了和历史数据以外,还和很多外界因素相关,比如用户的关注度,各国的政策,币圈之间是否打架等等。当然这些外界的因素不是我们这节课需要考虑的对象。
|
||||
|
||||
假设我们只考虑比特币以往的历史数据,用ARMA这个时间序列模型预测比特币的走势。
|
||||
|
||||
比特币历史数据(从2012-01-01到2018-10-31)可以从GitHub上下载:[https://github.com/cystanford/bitcoin](https://github.com/cystanford/bitcoin)。
|
||||
|
||||
你能看到数据一共包括了8个字段,代表的含义如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b0/36/b0db4047723ec5e649240e2a87196a36.png" alt=""><br>
|
||||
我们的目标是构造ARMA时间序列模型,预测比特币(平均)价格走势。p和q参数具体选择多少呢?我们可以设置一个区间范围,然后选择AIC最低的ARMA模型。
|
||||
|
||||
我们梳理下整个项目的流程:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/95/1e/95f8294c1f4805b86f9947178499181e.jpg" alt=""><br>
|
||||
首先我们需要加载数据。
|
||||
|
||||
在准备阶段,我们需要先探索数据,采用数据可视化方式查看比特币的历史走势。按照不同的时间尺度(天,月,季度,年)可以将数据压缩,得到不同尺度的数据,然后做可视化呈现。这4个时间尺度上,我们选择月作为预测模型的时间尺度,相应的,我们选择Weighted_Price这个字段的数值作为观察结果,在原始数据中,Weighted_Price对应的是比特币每天的平均价格,当我们以“月”为单位进行压缩的时候,对应的Weighted_Price得到的就是当月的比特币平均价格。压缩代码如下:
|
||||
|
||||
```
|
||||
df_month = df.resample('M').mean()
|
||||
|
||||
```
|
||||
|
||||
最后在预测阶段创建ARMA时间序列模型。我们并不知道p和q取什么值时,模型最优,因此我们可以给它们设置一个区间范围,比如都是range(0,3),然后计算不同模型的AIC数值,选择最小的AIC数值对应的那个ARMA模型。最后用这个最优的ARMA模型预测未来8个月的比特币平均价格走势,并将结果做可视化呈现。
|
||||
|
||||
基于这个流程,具体代码如下:
|
||||
|
||||
```
|
||||
# -*- coding: utf-8 -*-
|
||||
# 比特币走势预测,使用时间序列ARMA
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
from statsmodels.tsa.arima_model import ARMA
|
||||
import warnings
|
||||
from itertools import product
|
||||
from datetime import datetime
|
||||
warnings.filterwarnings('ignore')
|
||||
# 数据加载
|
||||
df = pd.read_csv('./bitcoin_2012-01-01_to_2018-10-31.csv')
|
||||
# 将时间作为df的索引
|
||||
df.Timestamp = pd.to_datetime(df.Timestamp)
|
||||
df.index = df.Timestamp
|
||||
# 数据探索
|
||||
print(df.head())
|
||||
# 按照月,季度,年来统计
|
||||
df_month = df.resample('M').mean()
|
||||
df_Q = df.resample('Q-DEC').mean()
|
||||
df_year = df.resample('A-DEC').mean()
|
||||
# 按照天,月,季度,年来显示比特币的走势
|
||||
fig = plt.figure(figsize=[15, 7])
|
||||
plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
|
||||
plt.suptitle('比特币金额(美金)', fontsize=20)
|
||||
plt.subplot(221)
|
||||
plt.plot(df.Weighted_Price, '-', label='按天')
|
||||
plt.legend()
|
||||
plt.subplot(222)
|
||||
plt.plot(df_month.Weighted_Price, '-', label='按月')
|
||||
plt.legend()
|
||||
plt.subplot(223)
|
||||
plt.plot(df_Q.Weighted_Price, '-', label='按季度')
|
||||
plt.legend()
|
||||
plt.subplot(224)
|
||||
plt.plot(df_year.Weighted_Price, '-', label='按年')
|
||||
plt.legend()
|
||||
plt.show()
|
||||
# 设置参数范围
|
||||
ps = range(0, 3)
|
||||
qs = range(0, 3)
|
||||
parameters = product(ps, qs)
|
||||
parameters_list = list(parameters)
|
||||
# 寻找最优ARMA模型参数,即best_aic最小
|
||||
results = []
|
||||
best_aic = float("inf") # 正无穷
|
||||
for param in parameters_list:
|
||||
try:
|
||||
model = ARMA(df_month.Weighted_Price,order=(param[0], param[1])).fit()
|
||||
except ValueError:
|
||||
print('参数错误:', param)
|
||||
continue
|
||||
aic = model.aic
|
||||
if aic < best_aic:
|
||||
best_model = model
|
||||
best_aic = aic
|
||||
best_param = param
|
||||
results.append([param, model.aic])
|
||||
# 输出最优模型
|
||||
result_table = pd.DataFrame(results)
|
||||
result_table.columns = ['parameters', 'aic']
|
||||
print('最优模型: ', best_model.summary())
|
||||
# 比特币预测
|
||||
df_month2 = df_month[['Weighted_Price']]
|
||||
date_list = [datetime(2018, 11, 30), datetime(2018, 12, 31), datetime(2019, 1, 31), datetime(2019, 2, 28), datetime(2019, 3, 31),
|
||||
datetime(2019, 4, 30), datetime(2019, 5, 31), datetime(2019, 6, 30)]
|
||||
future = pd.DataFrame(index=date_list, columns= df_month.columns)
|
||||
df_month2 = pd.concat([df_month2, future])
|
||||
df_month2['forecast'] = best_model.predict(start=0, end=91)
|
||||
# 比特币预测结果显示
|
||||
plt.figure(figsize=(20,7))
|
||||
df_month2.Weighted_Price.plot(label='实际金额')
|
||||
df_month2.forecast.plot(color='r', ls='--', label='预测金额')
|
||||
plt.legend()
|
||||
plt.title('比特币金额(月)')
|
||||
plt.xlabel('时间')
|
||||
plt.ylabel('美金')
|
||||
plt.show()
|
||||
|
||||
```
|
||||
|
||||
运行结果:
|
||||
|
||||
```
|
||||
Timestamp ... Weighted_Price
|
||||
Timestamp ...
|
||||
2011-12-31 2011-12-31 ... 4.471603
|
||||
2012-01-01 2012-01-01 ... 4.806667
|
||||
2012-01-02 2012-01-02 ... 5.000000
|
||||
2012-01-03 2012-01-03 ... 5.252500
|
||||
2012-01-04 2012-01-04 ... 5.208159
|
||||
|
||||
[5 rows x 8 columns]
|
||||
|
||||
```
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a9/2e/a91da037291f81fb6c90dc12223e9b2e.png" alt="">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f6/98/f6a288ca19200918dfe219d2be47af98.png" alt="">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a6/f9/a6648f60cf39dcba9fe81069da214cf9.png" alt=""><br>
|
||||
我们通过product函数创建了(p,q)在range(0,3)范围内的所有可能组合,并对每个ARMA(p,q)模型进行了AIC数值计算,保存了AIC数值最小的模型参数。然后用这个模型对比特币的未来8个月进行了预测。
|
||||
|
||||
从结果中你能看到,在2018年10月之后8个月的时间里,比特币会触底到4000美金左右,实际上比特币在这个阶段确实降低到了4000元美金甚至更低。在时间尺度的选择上,我们选择了月,这样就对数据进行了降维,也节约了ARMA的模型训练时间。你能看到比特币金额(美金)这张图中,按月划分的比特币走势和按天划分的比特币走势差别不大,在减少了局部的波动的同时也能体现出比特币的趋势,这样就节约了ARMA的模型训练时间。
|
||||
|
||||
## 总结
|
||||
|
||||
今天我给你讲了一个比特币趋势预测的实战项目。通过这个项目你应该能体会到,当我们对一个数值进行预测的时候,如果考虑的是多个变量和结果之间的关系,可以采用回归分析,如果考虑单个时间维度与结果的关系,可以使用时间序列分析。
|
||||
|
||||
根据比特币的历史数据,我们使用ARMA模型对比特币未来8个月的走势进行了预测,并对结果进行了可视化显示。你能看到ARMA工具还是很好用的,虽然比特币的走势受很多外在因素影响,比如政策环境。不过当我们掌握了这些历史数据,也不妨用时间序列模型来分析预测一下。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/24/94/24f8ee2f600a2451eecd58a98f7db894.png" alt=""><br>
|
||||
最后依然是思考题环节,今天我们讲了AR、MA、ARMA和ARIMA,你能简单说说它们之间的区别么?
|
||||
|
||||
另外我在[GitHub](https://github.com/cystanford/bitcoin)中上传了沪市指数的历史数据(对应的shanghai_1990-12-19_to_2019-2-28.csv),请你编写代码使用ARMA模型对沪市指数未来10个月(截止到2019年12月31日)的变化进行预测(将数据转化为按月统计即可)。
|
||||
|
||||
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
113
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/42丨当我们谈深度学习的时候,我们都在谈什么?.md
Normal file
113
极客时间专栏/数据分析实战45讲/第三模块:数据分析实战篇/42丨当我们谈深度学习的时候,我们都在谈什么?.md
Normal file
@@ -0,0 +1,113 @@
|
||||
<audio id="audio" title="42丨当我们谈深度学习的时候,我们都在谈什么?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/54/5e/541fcf4bf4b0ef0320f33b8b9eb32c5e.mp3"></audio>
|
||||
|
||||
在算法篇中,我们已经讲了数据挖掘十大经典算法,在实战篇中也了解了随机森林、逻辑回归的概念及工具使用。这些算法都属于传统的机器学习算法。你肯定听说过这两年很火的深度学习,那么机器学习算法和深度学习有什么关联呢?
|
||||
|
||||
在这篇文章中,我们会通过以下几个方面了解深度学习:
|
||||
|
||||
<li>
|
||||
数据挖掘、机器学习和深度学习的区别是什么?这些概念都代表什么?
|
||||
</li>
|
||||
<li>
|
||||
我们通过深度学习让机器具备人的能力,甚至某些技能的水平超过人类,比如图像识别、下棋对弈等。那么深度学习的大脑是如何工作的?
|
||||
</li>
|
||||
<li>
|
||||
深度学习是基于神经网络构建的,都有哪些常用的网络模型?
|
||||
</li>
|
||||
<li>
|
||||
深度学习有三个重要的应用领域,这三个应用领域分别是什么?
|
||||
</li>
|
||||
|
||||
## 数据挖掘,机器学习,深度学习的区别是什么?
|
||||
|
||||
实际上数据挖掘和机器学习在很大程度上是重叠的。一些常用算法,比如K-Means、KNN、SVM、决策树和朴素贝叶斯等,既可以说是数据挖掘算法,又可以说是机器学习算法。那么数据挖掘和机器学习之间有什么区别呢?
|
||||
|
||||
数据挖掘通常是从现有的数据中提取规律模式(pattern)以及使用算法模型(model)。核心目的是找到这些数据变量之间的关系,因此我们也会通过数据可视化对变量之间的关系进行呈现,用算法模型挖掘变量之间的关联关系。通常情况下,我们只能判断出来变量A和变量B是有关系的,但并不一定清楚这两者之间有什么具体关系。在我们谈论数据挖掘的时候,更强调的是从数据中挖掘价值。
|
||||
|
||||
机器学习是人工智能的一部分,它指的是通过训练数据和算法模型让机器具有一定的智能。一般是通过已有的数据来学习知识,并通过各种算法模型形成一定的处理能力,比如分类、聚类、预测、推荐能力等。这样当有新的数据进来时,就可以通过训练好的模型对这些数据进行预测,也就是通过机器的智能帮我们完成某些特定的任务。
|
||||
|
||||
深度学习属于机器学习的一种,它的目标同样是让机器具有智能,只是与传统的机器学习算法不同,它是通过神经网络来实现的。神经网络就好比是机器的大脑,刚开始就像一个婴儿一样,是一张白纸。但通过多次训练之后,“大脑”就可以逐渐具备某种能力。这个训练过程中,我们只需要告诉这个大脑输入数据是什么,以及对应的输出结果是什么即可。通过多次训练,“大脑”中的多层神经网络的参数就会自动优化,从而得到一个适应于训练数据的模型。
|
||||
|
||||
所以你能看到在传统的机器学习模型中,我们都会讲解模型的算法原理,比如K-Means的算法原理,KNN的原理等。而到了神经网络,我们更关注的是网络结构,以及网络结构中每层神经元的传输机制。我们不需要告诉机器具体的特征规律是什么,只需把我们想要训练的数据和对应的结果告诉机器大脑即可。**深度学习会自己找到数据的特征规律!而传统机器学习往往需要专家(我们)来告诉机器采用什么样的模型算法,这就是深度学习与传统机器学习最大的区别。**
|
||||
|
||||
另外深度学习的神经网络结构通常比较深,一般都是5层以上,甚至也有101层或更多的层数。这些深度的神经网络可以让机器更好地自动捕获数据的特征。
|
||||
|
||||
## 神经网络是如何工作的
|
||||
|
||||
神经网络可以说是机器的大脑,经典的神经网络结构可以用下面的图来表示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/06/b7/06d709117b776add64d1415ae209c0b7.jpg" alt=""><br>
|
||||
这里有一些概念你需要了解。
|
||||
|
||||
**节点**:神经网络是由神经元组成的,也称之为节点,它们分布在神经网络的各个层中,这些层包括输入层,输出层和隐藏层。
|
||||
|
||||
**输入层**:负责接收信号,并分发到隐藏层。一般我们将数据传给输入层。
|
||||
|
||||
**输出层**:负责输出计算结果,一般来说输出层节点数等于我们要分类的个数。
|
||||
|
||||
**隐藏层**:除了输入层和输出层外的神经网络都属于隐藏层,隐藏层可以是一层也可以是多层,每个隐藏层都会把前一层节点传输出来的数据进行计算(你可以理解是某种抽象表示),这相当于把数据抽象到另一个维度的空间中,可以更好地提取和计算数据的特征。
|
||||
|
||||
**工作原理**:神经网络就好比一个黑盒子,我们只需要告诉这个黑盒子输入数据和输出数据,神经网络就可以自我训练。一旦训练好之后,就可以像黑盒子一样使用,当你传入一个新的数据时,它就会告诉你对应的输出结果。在训练过程中,神经网络主要是通过前向传播和反向传播机制运作的。
|
||||
|
||||
什么是前向传播和反向传播呢?
|
||||
|
||||
**前向传播**:数据从输入层传递到输出层的过程叫做前向传播。这个过程的计算结果通常是通过上一层的神经元的输出经过矩阵运算和激活函数得到的。这样就完成了每层之间的神经元数据的传输。
|
||||
|
||||
**反向传播**:当前向传播作用到输出层得到分类结果之后,我们需要与实际值进行比对,从而得到误差。反向传播也叫作误差反向传播,核心原理是通过代价函数对网络中的参数进行修正,这样更容易让网络参数得到收敛。
|
||||
|
||||
所以,整个神经网络训练的过程就是不断地通过前向-反向传播迭代完成的,当达到指定的迭代次数或者达到收敛标准的时候即可以停止训练。然后我们就可以拿训练好的网络模型对新的数据进行预测。
|
||||
|
||||
当然,深度神经网络是基于神经网络发展起来的,它的原理与神经网络的原理一样,只不过强调了模型结构的深度,通常有5层以上,这样模型的学习能力会更强大。
|
||||
|
||||
## 常用的神经网络都有哪些
|
||||
|
||||
按照中间层功能的不同,神经网络可以分为三种网络结构,分别为FNN、CNN和RNN。
|
||||
|
||||
**FNN**(Fully-connected Neural Network)指的是全连接神经网络,全连接的意思是每一层的神经元与上一层的所有神经元都是连接的。不过在实际使用中,全连接的参数会过多,导致计算量过大。因此在实际使用中全连接神经网络的层数一般比较少。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c2/7a/c2bfd532360e5cc026aa12f45c86957a.jpg" alt=""><br>
|
||||
**CNN**叫作卷积神经网络,在图像处理中有广泛的应用,了解图像识别的同学对这个词一定不陌生。CNN网络中,包括了卷积层、池化层和全连接层。这三个层都有什么作用呢?
|
||||
|
||||
**卷积层**相当于一个滤镜的作用,它可以把图像进行分块,对每一块的图像进行变换操作。
|
||||
|
||||
**池化层**相当于对神经元的数据进行降维处理,这样输出的维数就会减少很多,从而降低整体的计算量。
|
||||
|
||||
**全连接层**通常是输出层的上一层,它将上一层神经元输出的数据转变成一维的向量。
|
||||
|
||||
**RNN**称为循环神经网络,它的特点是神经元的输出可以在下一个时刻作用到自身,这样RNN就可以看做是在时间上传递的神经网络。它可以应用在语音识别、自然语言处理等与上下文相关的场景。
|
||||
|
||||
深度学习网络往往包括了这三种网络的变种形成,常用的深度神经网络包括AlexNet、VGG19、GoogleNet、ResNet等,我总结了这些网络的特点,你可以看下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/49/9f/490ced74dc6cf70403e73c05f302449f.png" alt=""><br>
|
||||
你能看出随着时间的推进,提出的深度学习网络层数越来越深,Top-5错误率越来越低。
|
||||
|
||||
你可能会问什么是Top-5错误率,实际上这些网络结构的提出和一个比赛相关,这个比赛叫做ILSVRC,英文全称叫做Large Scale Visual Recognition Challenge。它是一个关于大规模图像可视化识别的比赛,所基于的数据集就是著名的ImageNet数据集,一共包括了1400万张图片,涵盖2万多个类别。
|
||||
|
||||
表格中的AlexNet就是2012年的ILSVRC冠军,当时的Top-5正确率是84.7%,VGG和GoogleNet是2014年ILSVRC比赛的模型,其中GoogleNet是当时比赛的冠军,而VGG是当时比赛的亚军,它的效率低于GoogleNet。VGG有两个版本,VGG16和VGG19,分别是16层和19层的VGG网络,这两者没有本质的区别,只是网络深度不同。到了2015年,比赛冠军是ResNet,Top-5正确率达到了96.43%。ResNet也有不同的版本,比如ResNet50、ResNet101和ResNet152等,名称后面的数字代表的是不同的网络深度。之后ResNet在其他图像比赛中也多次拿到冠军。
|
||||
|
||||
## 深度学习的应用领域
|
||||
|
||||
从ImageNet跑出来的这些优秀模型都是基于CNN卷积神经网络的。实际上深度学习有三大应用领域,图像识别就是其中之一,其他领域分别是语音识别和自然语言处理。
|
||||
|
||||
这三个应用领域有一个共同的特性,就是都来自于信号处理。我们人类平时会处理图像信息,语音信息以及语言文字信息。机器可以帮助我们完成这三个应用里的某些工作。比如图像识别领域中图像分类和物体检测就是两个核心的任务。我们可以让机器判断图像中都有哪些物体,类别是什么,以及这些物体所处的位置。图像识别被广泛应用在安防检测中。此外人脸识别也是图像识别重要的应用场景。
|
||||
|
||||
Siri大家一定不陌生,此外还有我们使用的智能电视等,都采用了语音识别技术。语音识别技术可以识别人类的语音指令并进行交互。在语音导航中,还采用了语音合成技术,这样就可以让机器模拟人的声音为我们服务,Siri语音助手也采用了语音识别和合成的技术。
|
||||
|
||||
自然语言处理的英文缩写是NLP,它被广泛应用到自动问答、智能客服、过滤垃圾邮件和短信等领域中。在电商领域,我们可以通过NLP自动给商品评论打标签,在用户决策的时候提供数据支持。在自动问答中,我们可以输入自己想问的问题,让机器来回答,比如在百度中输入“姚明的老婆”,就会自动显示出”叶莉“。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/24/34/24fe2dae1682cdbd5aa4a92124d88234.png" alt=""><br>
|
||||
此外这些技术还可以相互组合为我们提供服务,比如在无人驾驶中就采用了图像识别、语音识别等技术。在超市购物中也采用了集成图像识别、意图识别等技术等。
|
||||
|
||||
## 总结
|
||||
|
||||
今天我们大概了解了一下深度学习。深度学习也是机器学习的一种。我们之前讲解了数据挖掘十大经典算法,还有逻辑回归、随机森林算法等,这些都是传统的机器学习算法。在日常工作中,可以满足大部分的机器学习任务。但是对于数据量更大,更开放性的问题,我们就可以采用深度学习的算法,让机器自己来找规律,而不是通过我们指定的算法来找分类规律。
|
||||
|
||||
所以深度学习的普适性会更强一些,但也并不代表深度学习就优于机器学习。一方面深度学习需要大量的数据,另一方面深度学习的学习时间,和需要的计算资源都要大于传统的机器学习。你能看到各种深度学习的训练集一般都还是比较大的,比如ImageNet就包括了1400万张图片。如果我们没有提供大量的训练数据,训练出来的深度模型识别结果未必好于传统的机器学习。
|
||||
|
||||
实际上神经网络最早是在1986年提出来的,之后不温不火,直到ImageNet于2009年提出,在2010年开始举办每年的ImageNet大规模视觉识别挑战赛(ILSVRC),深度学习才得到迅猛发展。2016年Google研发的AlphaGo击败了人类冠军李世石,更是让人们看到了深度学习的力量。一个好问题的提出,可以激发无穷的能量,这是科技进步的源泉,也是为什么在科学上,我们会有各种公开的数据集。一个好的数据集就代表了一个好的问题和使用场景。正是这些需求的出现,才能让我们的算法有更好的用武之地,同时也有了各种算法相互比拼的平台。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e8/b9/e88beadebefd5789efb08284193f65b9.png" alt=""><br>
|
||||
今天我们聊了聊神经网络和深度学习,它们就像智能的黑盒子一样,通过训练可以帮我们处理各种问题,通过今天的学习,你能不能谈谈对深度学习工作原理的理解呢?以及深度学习都有哪些使用场景呢?
|
||||
|
||||
欢迎在评论区与我分享你的答案,也欢迎点击”请朋友读“,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
<audio id="audio" title="43丨深度学习(下):如何用Keras搭建深度学习网络做手写数字识别?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/32/96/3228defd1b859328a8b67706e1645696.mp3"></audio>
|
||||
|
||||
通过上节课的讲解,我们已经对神经网络和深度学习有了基本的了解。这节课我就用Keras这个深度学习框架做一个识别手写数字的练习。
|
||||
|
||||
你也许还有印象,在KNN算法那节中,我讲到过Mnist手写数字识别这个数据集,当时我们采用的是mini版的手写数字数据集。实际上完整版的Mnist一共有60000个训练样本和10000个测试样本,这么庞大的数据量更适合用深度学习框架完成训练。
|
||||
|
||||
今天的学习目标主要有以下的几个方面:
|
||||
|
||||
<li>
|
||||
进一步了解CNN网络。CNN网络在深度学习网络中应用很广,很多网络都是基于CNN网络构建的,你有必要进一步了解CNN的网络层次,尤其是关于卷积的原理。
|
||||
</li>
|
||||
<li>
|
||||
初步了解LeNet和AlexNet。它们都是经典的CNN网络,我们今天的任务就是认识这些经典的CNN网络,这样在接触更深度的CNN网络的时候,比如VGG、GoogleNet和ResNet这些网络的时候,就会更容易理解和使用。
|
||||
</li>
|
||||
<li>
|
||||
对常用的深度学习框架进行对比,包括Tensorflow、Keras、Caffe、PyTorch、 MXnet和Theano。当选择深度学习框架的时候到底该选择哪个?
|
||||
</li>
|
||||
<li>
|
||||
使用Keras这个深度学习框架编写代码,完成第一个深度学习任务,也就是Mnist手写数字识别。
|
||||
</li>
|
||||
|
||||
## 如何理解CNN网络中的卷积作用
|
||||
|
||||
CNN的网络结构由三种层组成,它们分别是卷积层、池化层和全连接层。
|
||||
|
||||
在上篇文章中,我讲到卷积层相当于滤镜的作用,它可以把图像分块,对每一块的图像进行卷积操作。
|
||||
|
||||
卷积本身是一种矩阵运算,那什么是卷积呢?
|
||||
|
||||
假设我有一个二维的图像X,和卷积K,把二维矩阵X进行卷积K操作之后,可以得到矩阵Z,如下图所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9d/cf/9d1bb65b30517775b632c10c1cb1c0cf.jpg" alt=""><br>
|
||||
我简单说下计算的原理。
|
||||
|
||||
第一步,我们需要将卷积核翻转180度(只有翻转之后才能做矩阵运算),也就是变成:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/cb/33/cb755c0cf5868c39d71e0392146c4833.jpg" alt=""><br>
|
||||
第二步,将卷积核的第一个元素,对准矩阵X左上角的第一个元素,对应元素相乘,然后再相加可以就可以得到10*1+10*1+10*0+10*1+5*0+5*-1+10*0+5*-1+5*-1=15。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/90/11/90a3bbabba732a2a7ad97a24f3587411.jpg" alt=""><br>
|
||||
第三步,每个元素都重复第二步的计算过程,可以得到如下的矩阵结果Z:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b8/6c/b824778383e3a898fe2399fb2eb8846c.jpg" alt=""><br>
|
||||
这样我们就完成了一个卷积的操作。如果编写代码的话,你可以这样写:
|
||||
|
||||
```
|
||||
import pylab
|
||||
import numpy as np
|
||||
from scipy import signal
|
||||
# 设置原图像
|
||||
img = np.array([[10, 10, 10, 10, 10],
|
||||
[10, 5, 5, 5, 10],
|
||||
[10, 5, 5, 5, 10],
|
||||
[10, 5, 5, 5, 10],
|
||||
[10, 10, 10, 10, 10]])
|
||||
# 设置卷积核
|
||||
fil = np.array([[ -1,-1, 0],
|
||||
[ -1, 0, 1],
|
||||
[ 0, 1, 1]])
|
||||
# 对原图像进行卷积操作
|
||||
res = signal.convolve2d(img, fil, mode='valid')
|
||||
# 输出卷积后的结果
|
||||
print(res)
|
||||
|
||||
```
|
||||
|
||||
运行结果:
|
||||
|
||||
```
|
||||
[[ 15 10 0]
|
||||
[ 10 0 -10]
|
||||
[ 0 -10 -15]]
|
||||
|
||||
```
|
||||
|
||||
这里我用到了convolve2d函数对图像img和卷积核fil做卷积运算,最后输出结果res。你可能还是会问,为什么我们要对图像进行卷积操作呢?你可以看看下面一段代码:
|
||||
|
||||
```
|
||||
import matplotlib.pyplot as plt
|
||||
import pylab
|
||||
import cv2
|
||||
import numpy as np
|
||||
from scipy import signal
|
||||
# 读取灰度图像
|
||||
img = cv2.imread("haibao.jpg", 0)
|
||||
# 显示灰度图像
|
||||
plt.imshow(img,cmap="gray")
|
||||
pylab.show()
|
||||
# 设置卷积核
|
||||
fil = np.array([[ -1,-1, 0],
|
||||
[ -1, 0, 1],
|
||||
[ 0, 1, 1]])
|
||||
# 卷积操作
|
||||
res = signal.convolve2d(img, fil, mode='valid')
|
||||
print(res)
|
||||
#显示卷积后的图片
|
||||
plt.imshow(res,cmap="gray")
|
||||
pylab.show()
|
||||
|
||||
```
|
||||
|
||||
运行结果:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/56/1d/562a68ac064736a6c00fab3808578b1d.png" alt="">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ec/2c/ecac01b4b72f0b5132294e3cfb9d562c.png" alt="">
|
||||
|
||||
这里我对专栏的海报做了卷积的操作,你能看到卷积操作是对图像进行了特征的提取。实际上每个卷积核都是一种滤波器,它们把图像中符合条件的部分筛选出来,也就相当于做了某种特征提取。
|
||||
|
||||
在CNN的卷积层中可以有多个卷积核,以LeNet为例,它的第一层卷积核有6个,因此可以帮我们提取出图像的6个特征,从而得到6个特征图(feature maps)。
|
||||
|
||||
### 激活函数的作用
|
||||
|
||||
做完卷积操作之后,通常还需要使用激活函数对图像进一步处理。在逻辑回归中,我提到过Sigmoid函数,它在深度学习中有广泛的应用,除了Sigmoid函数作为激活函数以外,tanh、ReLU都是常用的激活函数。
|
||||
|
||||
这些激活函数通常都是非线性的函数,使用它们的目的是把线性数值映射到非线性空间中。卷积操作实际上是两个矩阵之间的乘法,得到的结果也是线性的。只有经过非线性的激活函数运算之后,才能映射到非线性空间中,这样也可以让神经网络的表达能力更强大。
|
||||
|
||||
### 池化层的作用
|
||||
|
||||
池化层通常在两个卷积层之间,它的作用相当于对神经元的数据做降维处理,这样就能降低整体计算量。
|
||||
|
||||
假设池化的窗大小是2x2,就相当于用一个2x2的窗口对输出数据进行计算,将原图中2x2矩阵的4个点变成一个点。常用的池化操作是平均池化和最大池化。平均池化是对特征点求平均值,也就是用4个点的平均值来做代表。最大池化则是对特征点求最大值,也就是用4个点的最大值来做代表。
|
||||
|
||||
在神经网络中,我们可以叠加多个卷积层和池化层来提取更抽象的特征。经过几次卷积和池化之后,通常会有一个或多个全连接层。
|
||||
|
||||
### 全连接层的作用
|
||||
|
||||
全连接层将前面一层的输出结果与当前层的每个神经元都进行了连接。
|
||||
|
||||
这样就可以把前面计算出来的所有特征,通过全连接层将输出值输送给分类器,比如Softmax分类器。在深度学习中,Softmax是个很有用的分类器,通过它可以把输入值映射到0-1之间,而且所有输出结果相加等于1。其实你可以换种方式理解这个概念,假设我们想要识别一个数字,从0到9都有可能。那么通过Softmax层,对应输出10种分类结果,每个结果都有一个概率值,这些概率相加为1,我们就可以知道这个数字是0的概率是多少,是1的概率是多少……是9的概率又是多少,从而也就帮我们完成了数字识别的任务。
|
||||
|
||||
## LeNet和AlexNet网络
|
||||
|
||||
你能看出CNN网络结构中每一层的作用:它通过卷积层提取特征,通过激活函数让结果映射到非线性空间,增强了结果的表达能力,再通过池化层压缩特征图,降低了网络复杂度,最后通过全连接层归一化,然后连接Softmax分类器进行计算每个类别的概率。
|
||||
|
||||
通常我们可以使用多个卷积层和池化层,最后再连接一个或者多个全连接层,这样也就产生了不同的网络结构,比如LeNet和AlexNet。
|
||||
|
||||
我将LeNet和AlexNet的参数特征整理如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/dd/d1/dd0dbbcd6797bf9560c306025ee6fbd1.png" alt=""><br>
|
||||
LeNet提出于1986年,是最早用于数字识别的CNN网络,输入尺寸是32*32。它输入的是灰度的图像,整个的网络结构是:输入层→C1卷积层→S2池化层→C3卷积层→S4池化层→C5卷积层→F6全连接层→Output全连接层,对应的Output输出类别数为10。
|
||||
|
||||
AlexNet在LeNet的基础上做了改进,提出了更深的CNN网络模型,输入尺寸是227*227*3,可以输入RGB三通道的图像,整个网络的结构是:输入层→(C1卷积层→池化层)→(C2卷积层→池化层)→C3卷积层→C4卷积层→(C5池化层→池化层)→全连接层→全连接层→Output全连接层。
|
||||
|
||||
实际上后面提出来的深度模型,比如VGG、GoogleNet和ResNet都是基于下面的这种结构方式改进的:输出层→(卷积层+ -> 池化层?)+ → 全连接层+→Output全连接层。
|
||||
|
||||
其中“+”代表1个或多个,“?”代表0个或1个。
|
||||
|
||||
你能看出卷积层后面可以有一个池化层,也可以没有池化层,“卷积层+ → 池化层?”这样的结构算是一组卷积层,在多组卷积层之后,可以连接多个全连接层,最后再接Output全连接层。
|
||||
|
||||
## 常用的深度学习框架对比
|
||||
|
||||
了解了CNN的网络结构之后,我们来看下常用的深度学习框架都有哪些。
|
||||
|
||||
下面这张图是常用框架的简单对比。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ea/67/ea523df67c73d19732df1d172b30fd67.png" alt=""><br>
|
||||
从GitHub上的热门程序排序来看,Tensorflow、Keras和Caffe是三个排名最高的深度学习框架,其中Tensorflow是Google出品,也是深度学习最常用的库。关于Keras,你可以理解成是把Tensorflow或Theano作为后端,基于它们提供的封装接口,这样更方便我们操作使用。Caffe、PyTorch、MXNet和Theano也是常用的深度学习库,你在接触深度学习的时候可能也会遇到,这里不做介绍。
|
||||
|
||||
如果你刚进入深度学习这个领域,我更建议你直接使用Keras,因为它使用方便,更加友好,可以方便我们快速构建网络模型,不需要过多关注底层细节。
|
||||
|
||||
## 用Keras做Mnist手写数字识别
|
||||
|
||||
Keras也是基于Python语言的。在使用Keras之前,我们需要安装相应的工具包:
|
||||
|
||||
```
|
||||
pip install keras
|
||||
pip install tensorflow
|
||||
|
||||
```
|
||||
|
||||
这里需要注明的是Keras需要用tensorflow或者theano作为后端,因此我们也需要引入相关的工具。同时你还需要注意NumPy版本是否为最新的版本,我们需要采用最新的NumPy版本才能正常运行keras,更新NumPy工具的方法:
|
||||
|
||||
```
|
||||
pip install -U numpy
|
||||
|
||||
```
|
||||
|
||||
安装好Keras工具包之后,就可以创建一个Sequential序贯模型,它的作用是将多个网络层线性堆叠起来,使用方法:
|
||||
|
||||
```
|
||||
from keras.models import Sequential
|
||||
model = Sequential()
|
||||
|
||||
```
|
||||
|
||||
然后就可以在网络中添加各种层了。
|
||||
|
||||
### 创建二维卷积层
|
||||
|
||||
使用Conv2D(filters, kernel_size, activation=None)进行创建,其中filters代表卷积核的数量,kernel_size代表卷积核的宽度和长度,activation代表激活函数。如果创建的二维卷积层是第一个卷积层,我们还需要提供input_shape参数,比如:input_shape=(28, 28, 1)代表的就是28*28的灰度图像。
|
||||
|
||||
### 对2D信号做最大池化层
|
||||
|
||||
使用MaxPooling2D(pool_size=(2, 2))进行创建,其中pool_size代表下采样因子,比如pool_size=(2,2)的时候相当于将原来2**2的矩阵变成一个点,即用2**2矩阵中的最大值代替,输出的图像在长度和宽度上均为原图的一半。
|
||||
|
||||
### 创建Flatten层
|
||||
|
||||
使用Flatten()创建,常用于将多维的输入扁平化,也就是展开为一维的向量。一般用在卷积层与全连接层之间,方便后面进行全连接层的操作。
|
||||
|
||||
### 创建全连接层
|
||||
|
||||
使用Dense(units, activation=None)进行创建,其中units代表的是输出的空间维度,activation代表的激活函数。
|
||||
|
||||
我这里只列举了部分常用的层,这些层在今天手写数字识别的项目中会用到。当我们把层创建好之后,可以加入到模型中,使用model.add()函数即可。
|
||||
|
||||
添加好网络模型中的层之后,我们可以使用model.compile(loss, optimizer=‘adam’, metrics=[‘accuracy’])来完成损失函数和优化器的配置,其中loss代表损失函数的配置,optimizer代表优化器,metrics代表评估模型所采用的指标。
|
||||
|
||||
然后我们可以使用fit函数进行训练,使用predict函数进行预测,使用evaluate函数对模型评估。
|
||||
|
||||
针对Mnist手写数字识别,用keras的实现代码如下:
|
||||
|
||||
```
|
||||
# 使用LeNet模型对Mnist手写数字进行识别
|
||||
import keras
|
||||
from keras.datasets import mnist
|
||||
from keras.layers import Conv2D, MaxPooling2D
|
||||
from keras.layers import Dense, Flatten
|
||||
from keras.models import Sequential
|
||||
# 数据加载
|
||||
(train_x, train_y), (test_x, test_y) = mnist.load_data()
|
||||
# 输入数据为 mnist 数据集
|
||||
train_x = train_x.reshape(train_x.shape[0], 28, 28, 1)
|
||||
test_x = test_x.reshape(test_x.shape[0], 28, 28, 1)
|
||||
train_x = train_x / 255
|
||||
test_x = test_x / 255
|
||||
train_y = keras.utils.to_categorical(train_y, 10)
|
||||
test_y = keras.utils.to_categorical(test_y, 10)
|
||||
# 创建序贯模型
|
||||
model = Sequential()
|
||||
# 第一层卷积层:6个卷积核,大小为5∗5, relu激活函数
|
||||
model.add(Conv2D(6, kernel_size=(5, 5), activation='relu', input_shape=(28, 28, 1)))
|
||||
# 第二层池化层:最大池化
|
||||
model.add(MaxPooling2D(pool_size=(2, 2)))
|
||||
# 第三层卷积层:16个卷积核,大小为5*5,relu激活函数
|
||||
model.add(Conv2D(16, kernel_size=(5, 5), activation='relu'))
|
||||
# 第二层池化层:最大池化
|
||||
model.add(MaxPooling2D(pool_size=(2, 2)))
|
||||
# 将参数进行扁平化,在LeNet5中称之为卷积层,实际上这一层是一维向量,和全连接层一样
|
||||
model.add(Flatten())
|
||||
model.add(Dense(120, activation='relu'))
|
||||
# 全连接层,输出节点个数为84个
|
||||
model.add(Dense(84, activation='relu'))
|
||||
# 输出层 用softmax 激活函数计算分类概率
|
||||
model.add(Dense(10, activation='softmax'))
|
||||
# 设置损失函数和优化器配置
|
||||
model.compile(loss=keras.metrics.categorical_crossentropy, optimizer=keras.optimizers.Adam(), metrics=['accuracy'])
|
||||
# 传入训练数据进行训练
|
||||
model.fit(train_x, train_y, batch_size=128, epochs=2, verbose=1, validation_data=(test_x, test_y))
|
||||
# 对结果进行评估
|
||||
score = model.evaluate(test_x, test_y)
|
||||
print('误差:%0.4lf' %score[0])
|
||||
print('准确率:', score[1])
|
||||
|
||||
```
|
||||
|
||||
运行结果:
|
||||
|
||||
```
|
||||
……(省略中间迭代的结算结果,即显示每次迭代的误差loss和准确率acc)
|
||||
误差:0.0699
|
||||
准确率: 0.9776
|
||||
|
||||
```
|
||||
|
||||
我用epochs控制了训练的次数,当训练2遍的时候,准确率达到97.76%,还是很高的。
|
||||
|
||||
## 总结
|
||||
|
||||
今天我们用keras对手写数字进行了识别,具体的代码部分讲解的不多,其中涉及到API,你可以参考下Keras中文手册。
|
||||
|
||||
在这个过程里,我们只是使用了LeNet的网络模型,实际上AlexNet、VGG、GoogleNet和ResNet都是基于CNN的网络结构。在CNN网络中包括了卷积层、池化层和全连接层。一个基于CNN的深度学习网络通常是几组卷积层之后,再连接多个全连接层,最后再接Output全连接层,而每组的卷积层都是“卷积层+ →池化层?”的结构。
|
||||
|
||||
另外,通过今天的学习你应该能体会到卷积在图像领域中的应用。今天我对专栏的海报进行了一个3*3的卷积核操作,可以看到卷积之后得到的图像是原图像某种特征的提取。在实际的卷积层中,会包括多个卷积核,对原图像在不同特征上进行提取。通过多个卷积层的操作,可以在更高的维度上对图像特征进一步提取,这样可以让机器在不同层次、不同维度理解图像特征。
|
||||
|
||||
另外在Keras使用中,你能看到与sklearn中的机器学习算法使用不同。我们需要对网络模型中的层进行配置,将创建好的层添加到模型中,然后对模型中使用的损失函数和优化器进行配置,最后就可以对它进行训练和预测了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/43/39/431ccdf001d421b3810e03c9c598b539.png" alt=""><br>
|
||||
今天讲的知识点比较多,其中我讲到了卷积、卷积核和卷积层,你能说一下对这三者的理解吗?你之前有使用Keras或Tensorflow的经验么,你能否谈谈你的使用感受?
|
||||
|
||||
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user