This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View 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(&quot;loginName&quot;).send_keys(username)
browser.find_element_by_id(&quot;loginPassword&quot;).send_keys(password)
time.sleep(1)
# 点击登录
browser.find_element_by_id(&quot;loginAction&quot;).click()
time.sleep(1)
# 设置用户名、密码
username = 'XXXX'
password = &quot;XXXX&quot;
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(&quot;follow&quot;).click()
follow_button = browser.find_element_by_xpath('//div[@class=&quot;m-add-box m-followBtn&quot;]')
follow_button.click()
time.sleep(1)
# 选择分组
group_button = browser.find_element_by_xpath('//div[@class=&quot;m-btn m-btn-white m-btn-text-black&quot;]')
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(&quot;textarea.W_input&quot;).clear()
content_textarea = browser.find_element_by_css_selector(&quot;textarea.W_input&quot;).send_keys(content)
time.sleep(2)
comment_button = browser.find_element_by_css_selector(&quot;.W_btn_a&quot;).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(&quot;[node-type='publish']&quot;).click()
# 在弹出的文本框中输入内容
content_textarea = browser.find_element_by_css_selector(&quot;textarea.W_input&quot;).send_keys(content)
time.sleep(2)
# 点击发布按钮
post_button = browser.find_element_by_css_selector(&quot;[node-type='submit']&quot;).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做取消关注的操作请你使用今天讲的元素定位和操作功能编写相应的代码。
通过今天自动化测试工具的学习,你有怎样的收获和总结呢?
欢迎在评论区与我分享你的答案,也欢迎点击”请朋友读“,把这篇文章分享给你的朋友或者同事,一起实战交流一下。

View 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(&quot;off&quot;)
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 = &quot; &quot;.join(jieba.cut(f,cut_all=False, HMM=True))
wc = WordCloud(
font_path=&quot;./SimHei.ttf&quot;,
max_words=100,
width=2000,
height=1200,
)
wordcloud = wc.generate(text)
# 写词云图片
wordcloud.to_file(&quot;wordcloud.jpg&quot;)
# 显示词云文件
plt.imshow(wordcloud)
plt.axis(&quot;off&quot;)
plt.show()
f = '数据分析全景图及修炼指南\
学习数据挖掘的最佳学习路径是什么?\
Python基础语法开始你的Python之旅\
Python科学计算NumPy\
Python科学计算Pandas\
学习数据分析要掌握哪些基本概念?\
用户画像:标签化就是数据的抽象能力\
数据采集:如何自动化采集数据?\
数据采集如何用八爪鱼采集微博上的“D&amp;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 = &quot; &quot;.join(jieba.cut(f,cut_all=False, HMM=True))
wc = WordCloud(
font_path=&quot;./wc.ttf&quot;,
max_words=100,
width=2000,
height=1200,
)
print(cut_text)
wordcloud = wc.generate(cut_text)
# 写词云图片
wordcloud.to_file(&quot;wordcloud.jpg&quot;)
# 显示词云文件
plt.imshow(wordcloud)
plt.axis(&quot;off&quot;)
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 = &quot;//*[@id='hotsong-list']//a/@href&quot;
name_xpath = &quot;//*[@id='hotsong-list']//a/text()&quot;
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&amp;id=' + song_id + '&amp;lv=-1&amp;kv=-1&amp;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&amp;id=536099160&amp;lv=-1&amp;kv=-1&amp;tv=-1](http://music.163.com/api/song/lyric?os=pc&amp;id=536099160&amp;lv=-1&amp;kv=-1&amp;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
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View 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 = {&quot;n_estimators&quot;: range(1,11)}
iris = load_iris()
# 使用GridSearchCV进行参数调优
clf = GridSearchCV(estimator=rf, param_grid=parameters)
# 对iris数据集进行分类
clf.fit(iris.data, iris.target)
print(&quot;最优分数: %.4lf&quot; %clf.best_score_)
print(&quot;最优参数:&quot;, 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 = {&quot;randomforestclassifier__n_estimators&quot;: 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(&quot;最优分数: %.4lf&quot; %clf.best_score_)
print(&quot;最优参数:&quot;, 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(&quot;pastel&quot;)
sns.barplot(x = 'default.payment.next.month', y=&quot;values&quot;, 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(&quot;GridSearch最优参数&quot;, search.best_params_)
print(&quot;GridSearch最优分数 %0.4lf&quot; %search.best_score_)
predict_y = gridsearch.predict(test_x)
print(&quot;准确率 %0.4lf&quot; %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运行看看最优参数是多少测试准确率是多少
欢迎你在评论区与我分享你的答案,如果有问题也可以写在评论区。如果你觉得这篇文章有价值,欢迎把它分享给你的朋友或者同事。

View 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个特征V1V2……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&quot;', 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] &gt; 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=37FP=7FN=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值。
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View 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模型还存在一个阶数称为ARp模型也叫作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模型中文叫差分自回归滑动平均模型也叫求合自回归滑动平均模型。相比于ARMAARIMA多了一个差分的过程作用是对不平稳数据进行差分平稳在差分平稳后再进行建模。ARIMA的原理和ARMA模型一样。相比于ARMA(p,q)的两个阶数ARIMA是一个三元组的阶数(p,d,q)称为ARIMA(p,d,q)模型。其中d是差分阶数。
## ARMA模型工具
上面介绍的ARMAARMAARIMA四种模型你只需要了解基础概念即可中间涉及到的一些数学公式这里不进行展开。
在实际工作中我们更多的是使用工具我在这里主要讲解下如何使用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(&quot;inf&quot;) # 正无穷
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 &lt; 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日的变化进行预测将数据转化为按月统计即可
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。

View 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年比赛冠军是ResNetTop-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>
今天我们聊了聊神经网络和深度学习,它们就像智能的黑盒子一样,通过训练可以帮我们处理各种问题,通过今天的学习,你能不能谈谈对深度学习工作原理的理解呢?以及深度学习都有哪些使用场景呢?
欢迎在评论区与我分享你的答案,也欢迎点击”请朋友读“,把这篇文章分享给你的朋友或者同事。

View File

@@ -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(&quot;haibao.jpg&quot;, 0)
# 显示灰度图像
plt.imshow(img,cmap=&quot;gray&quot;)
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=&quot;gray&quot;)
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都是基于下面的这种结构方式改进的输出层→卷积层+ -&gt; 池化层?)+ → 全连接层+→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个卷积核大小为55, 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*5relu激活函数
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的经验么你能否谈谈你的使用感受
欢迎你在评论区与我分享你的答案,也欢迎点击“请朋友读”,把这篇文章分享给你的朋友或者同事。