冲积图
随着时间的推移绘制路径
我与人合著的一篇论文上周发表在 T4 的《儿童心理学和精神病学杂志》上。本文报告了一项关于学校支持干预对肯尼亚孤儿抑郁症影响的二次分析结果。在为公共存档准备数据的过程中,我认为尝试将我们关于萧条的纵向数据可视化为冲积图会很不错。
下图是 r 的[alluvial](https://github.com/mbojan/alluvial)
包的作者 Michał Bojanowski 创建的Titanic
数据集冲积图的示例。该图从乘客是否幸存开始向后流动。例如,你可以从Survived=="No"
追溯到泰坦尼克号的大部分船员都没有生还。
Alluvial diagram of the Titanic dataset created by Michal Bojanowski.
我将向您展示如何使用这个包来创建一个不同类型的冲积图,描述个人如何跨时间点流动。
数据
我的同事在肯尼亚设计了一项随机分组试验,招募了 26 所小学的 800 多名青少年参与学校支持(即免学费、校服和护士出诊)研究。作为基线调查的一部分,孩子们被问及一份名为 CESD-R 的问卷中的一部分问题,以评估抑郁症的严重程度。然后,一半的学校被随机分配接受学校支持干预,研究小组跟踪这些孩子超过四年。
在我们的二次分析中,我们创建了一个抑郁指标,它是五个 CESD-R 问题的平均值,这些问题被重新调整为 0 到 3 的范围。分数越高,表示严重性越高。
群体意味着随着时间的推移
这是我们论文中的图 1,显示了按研究组和年份划分的平均抑郁分数。这是试验数据的简明摘要,但它并没有给我们一种个体如何随时间变化的感觉。所有的信息都被压缩成了。
从一年级到四年级抑郁状态的变化
该线图显示,平均而言,各组在严重程度方面是相似的,直到第 4 年,当对照组的分数增加时,产生了与治疗组的分离。让我们来探索一下第 4 年发生了什么。
查看数据的一种方法是根据参与者的年度严重程度评分将他们标记为“可能抑郁”或“没有抑郁”,并将研究组在第 4 年的这种状态与他们在第 1 年开始时的状态进行比较。这就是我们在图 3 中所做的,将每个人标记为:
- 从未抑郁(低于第 1 年和第 4 年可能抑郁的分数线)
- 仍然低迷(高于第 1 年和第 4 年的临界值)
- 下降(低于第 1 年的临界值,但高于第 4 年)
- 缓解(高于第 1 年的临界值,但低于第 4 年的临界值)
个体变化
这个数字暗示了一个事实,即参与者对他们的第四年抑郁分数有不同的途径,但数据仍然是按组总结的。让我们试着去掉所有的汇总,显示每个青少年在每个时间点的分数。
下面是获取原始数据并将其整理成正确形状以便绘图的脚本:
现在我们可以把每个人的年度得分画成一条线:
Individual scores over time faceted by study arm. The blue line is participant #2, and the red line is participant #3. Everyone else is grey.
真是一团糟!数据科学可能既是艺术又是科学,但这个情节完全属于艺术阵营。
更细致的聚合
现在,我将使用alluvial
包按照路径将参与者聚集在一起。代码如下:
为了使事情简单,这个情节没有在学习臂上刻面,但是这样的刻面是可能的。要把握的更大图景是冲积图如何追踪参与者在纵向数据集中采取的所有可能路径。
例如,得分从未超过抑郁临界值的青少年在底部形成一条灰色直线带,从第 4 年的“不抑郁”一直回到第 3、2 和 1 年的“不抑郁”。有一个平行的一直按下的组,由顶部的直线红带代表。中间的波浪是一次或多次改变状态的参与者。
下次您看到基于前/后数据的报告时,您可能会想知道在两次观察之间发生了什么。如果你真的收集纵向数据,试着用冲积图绘制这种变化。
Tweep & Vader 的(几乎)实时 Twitter 情绪分析
这篇文章的想法是捕捉推文,根据最常用的词和标签对它们进行分析,并根据它们的情绪(积极、消极或中立)对它们进行分类。我们将不仅使用从历史数据库中捕获的推文作为数据集,例如, @realDonaldTrump: 发送的最后 200 条推文
还包括在某一特定时刻生成的所有实时推文,例如,在包含作品川普或墙的纽约州地区发送的推文
对于情感分析,我们将使用 VADER ( 效价感知词典和情感推理机),这是一个基于词典和规则的情感分析工具*,专门针对社交媒体中表达的情感*。对于 Tweepy 捕获, API Tweepy 将是被选中的那个!
使用 Vader 进行情感分析
安装 Vader 的最简单方法是使用 pip 命令:
pip install vaderSentiment
接下来,让我们调用库并创建“分析器”:
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzeranalyser = SentimentIntensityAnalyzer()
您只需在下面的函数中输入一个文本字符串即可获得分数:
analyser.polarity_scores("The movie is good")
结果将是一个字典:
{'compound': 0.4404, 'neg': 0.0, 'neu': 0.508, 'pos': 0.492}
上面的结果意味着这个句子几乎是半肯定的(’ pos’: 0.492),或多或少是中性的(’ neu’: 0.508),而绝不是否定的(’ neg’: 0.0)。事实上,最重要的结果是分数:“复合”,它可以表明文本是“好的”(大于零的值)。让我们来分析同一句话,但带有负面情绪:
analyser.polarity_scores("The movie is very bad")
结果是:
{'compound': -0.5849, 'neg': 0.487, 'neu': 0.513, 'pos': 0.0}
因此,我们得出结论,只寻找复合的结果,文本必须表现出消极情绪。
不仅如此,你还可以有不同程度的这种情绪:
“电影很烂”== >复合:-0.5849
“电影很烂”== >复合:-0.7398
“这部电影很糟糕!!!"== >复合:-0.7984
诸如此类…
简而言之,正面、负面和中性分数表示属于这些类别的文本的比例,并且复合分数是计算所有词典评级的总和的度量,这些评级已经在-1(最极端负面)和+1(最极端正面)之间标准化。
关于 Vader 的更详细的教程,请参见这篇中型文章:在 Python 中使用 VADER 简化情感分析
回到我们的分析,复合分数的范围是[-1,1],即:
- [-1 到 0):负数,
- (0 到+1):正
因此,让我们编写一个函数来仅捕获通用文本的这 3 种状态:
def sentiment_analyzer_scores(text):
score = analyser.polarity_scores(text)
lb = score['compound']
if lb >= 0.05:
return 1
elif (lb > -0.05) and (lb < 0.05):
return 0
else:
return -1
测试…
- 情操 _ 分析器 _ 分数(“电影很烂!”)== >结果:-1
- 情操 _ 分析器 _ 分数(“电影很长!!!")== >结果:0
- 感悟 _ 分析器 _ 分数(“电影很好看!”)== >结果:1
在其他语言中使用 Vader
Vader 确实是一个很好的工具,但不幸的是,它是建立在英语之上的(Vader 不能直接与其他语言一起工作)。
但如果你生活或工作在说其他语言的国家,你可以很容易地创建一个“转机”,并在应用 Vader 之前将你的文本从原始语言翻译成英语。
为此,我们将使用 *Googletrans,*一个实现了 Google Translate API 的免费且无限制的 python 库(详情请参考 API 文档)。
要安装 Googletrans,您可以使用 pip 命令:
pip install googletrans
和我们对维达做的一样,让我们导入库并调用翻译器:
from googletrans import Translator
translator = Translator()
让我们测试一个简单的西班牙语翻译:
translator.translate('hola, todo bien?').text
结果是:
'hello, all right?'
让我们尝试对一段西班牙文本进行“情感分析”:“la pelicula es mala”(“电影很糟糕”)
text = translator.translate('la pelicula es mala').text
analyser.polarity_scores(text)
结果是:
{'compound': -0.5423, 'neg': 0.538, 'neu': 0.462, 'pos': 0.0}
和我们一开始得到的结果完全一样!太好了!因此,我们可以将之前的函数更新到现在,还可以获得任何语言的任何文本的情感分析!
def sentiment_analyzer_scores(text, engl=True):
if engl:
trans = text
else:
trans = translator.translate(text).text score = analyser.polarity_scores(trans)
lb = score['compound']
if lb >= 0.05:
return 1
elif (lb > -0.05) and (lb < 0.05):
return 0
else:
return -1
请注意,首先,我测试了语言是否是“英语”,如果是,不需要翻译,我们可以直接使用维达,即使没有互联网连接。当然,对于其他语言,互联网连接是强制性的,因为 Google Translate API 需要访问其在线服务。
您可以通知翻译您正在使用的语言,但在我们的情况下,我们将把这项工作留给做得很好的 Google(自动语言检测)。例如,让我们用葡萄牙语测试文本:“今天天气很好,阳光充足。”:
text = 'o dia esta lindo, com muito sol'
sentiment_analyzer_scores(text, False)
将结果为 1(“积极情绪”)。
太好了!在这一点上,我们可以分析几乎任何语言文本背后的情感!那么,为什么不从推文中提取“文本”呢?这将是我们的下一步行动!
准备 Tweepy 来捕捉推文
首先,让我们安装 Tweeppy:
pip install tweepy
需要指出的是,Twitter 要求所有请求都使用 Oauth 进行身份验证。本教程考虑到你实际上是一名 Twitter 开发者,拥有访问 tweets 的所有必要“钥匙”。
Tweepy 尽量让 OAuth 对你来说不那么痛苦。要开始这个过程,我们需要向 Twitter 注册我们的客户端应用程序。创建一个新的应用程序,一旦你完成,你应该有你的消费者令牌和秘密。把这两个放在手边,你会需要它们的。更多详情请前往认证教程。
安装 Tweepy 并准备好所有令牌后,让我们开始:
import tweep
获得授权:
consumer_key = 'YOUR KEY HERE'
consumer_secret = 'YOUR KEY HERE'
access_token = 'YOUR KEY HERE'
access_token_secret = 'YOUR KEY HERE'auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)api = tweepy.API(auth)
就是这样!您已经准备好捕捉推文了!
从 id 中读取和分析推文
首先,我将从我的大学获得几条推文:
tweets = api.user_timeline('[@ingenieriaUDD_](http://twitter.com/ingenieriaUDD_)', count=5, tweet_mode='extended')
for t in tweets:
print(t.full_text)
print()
结果是:
太好了!但是,仅仅打印推文并不能帮助我们走上“数据科学征服之路”!我们需要将它们放在一个数据集上(在这一点上,只是一个列表)以供将来分析。因此,一个简单的函数可以帮助我们:
def list_tweets(user_id, count, prt=False):
tweets = api.user_timeline(
"@" + user_id, count=count, tweet_mode='extended')
tw = []
for t in tweets:
tw.append(t.full_text)
if prt:
print(t.full_text)
print()
return tw
分析一下唐纳德·特朗普发的推文怎么样?
user_id = ‘realDonaldTrump’
count=200tw_trump = list_tweets(user_id, count)
在 tw_trump 上,我们将有一个列表,其中每个列表项都是 trump 的一条推文。例如,让我们看看我们的列表中保存的 200 条推文中的一条,在这种情况下,第三条推文被捕获:
嗯,这是可以的,但我们可以看到,有一些推文的部分,事实上并没有帮助我们分析它的情绪,如网址,其他一些用户标识,数字等。我们应该做一些清洁工作:
def remove_pattern(input_txt, pattern):
r = re.findall(pattern, input_txt)
for i in r:
input_txt = re.sub(i, '', input_txt)
return input_txtdef clean_tweets(lst):
# remove twitter Return handles (RT [@xxx](http://twitter.com/xxx):)
lst = np.vectorize(remove_pattern)(lst, "RT @[\w]*:")
# remove twitter handles ([@xxx](http://twitter.com/xxx))
lst = np.vectorize(remove_pattern)(lst, "@[\w]*")
# remove URL links (httpxxx)
lst = np.vectorize(remove_pattern)(lst, "https?://[A-Za-z0-9./]*")
# remove special characters, numbers, punctuations (except for #)
lst = np.core.defchararray.replace(lst, "[^a-zA-Z#]", " ")return lst
现在,我们干净的推特:
这条推特上的情绪怎么样?
是的,显然是积极的情绪(“1”)。
当然,我们可以比这好得多。让我们创建一个函数来捕捉和显示唐纳德·特朗普最后 200 条推文的情绪:
def anl_tweets(lst, title='Tweets Sentiment', engl=True ):
sents = []
for tw in lst:
try:
st = sentiment_analyzer_scores(tw, engl)
sents.append(st)
except:
sents.append(0)
ax = sns.distplot(
sents,
kde=False,
bins=3)
ax.set(xlabel='Negative Neutral Positive',
ylabel='#Tweets',
title="Tweets of @"+title)
return sents
这个函数的返回是一个列表,其中每个 tweet 的情感评分结果(-1、0 或 1)用作输入参数。
用词云分析推文
另一个有趣的快速分析是从一系列推文中产生的“词汇云”中获取一个峰值。为此,我们将使用 word_cloud ,这是 Python 中的一个小单词云生成器。在博客或网站上了解更多信息。
首先,安装 word_cloud:
pip install wordcloud
现在,让我们创建一个从 tweet 列表生成单词云的通用函数:
def word_cloud(wd_list):
stopwords = set(STOPWORDS)
all_words = ' '.join([text for text in wd_list])
wordcloud = WordCloud(
background_color='white',
stopwords=stopwords,
width=1600,
height=800,
random_state=21,
colormap='jet',
max_words=50,
max_font_size=200).generate(all_words) plt.figure(figsize=(12, 10))
plt.axis('off')
plt.imshow(wordcloud, interpolation="bilinear");
现在我们已经定义了所有的函数,我们可以对任何一个 tweetser 生成的任何一组 tweet 进行重复分析。让我们对奥巴马最后的 200 条推文做同样的尝试:
特定过滤器的流推文
twitter 流 API 用于实时下载 Twitter 消息。这对于获取大量的 tweets,或者使用站点流或用户流创建实时提要非常有用。Tweepy 通过处理身份验证、连接、创建和销毁会话、读取传入消息和部分路由消息,使使用 twitter 流 API 变得更加容易。
创建 tweet 实时监听器最重要的参数:
轨道
以逗号分隔的短语列表,将用于确定哪些推文将在流上发布。一个短语可以是由空格分隔的一个或多个术语,如果该短语中的所有术语都出现在 Tweet 中,则该短语将匹配,而不管顺序和大小写。根据这个模型,你可以把逗号想象成逻辑 OR,而空格相当于逻辑 AND(例如‘the Twitter’是 AND twitter,而‘the,Twitter’是 OR twitter)。
语言
除非明确说明,否则此参数可用于所有流式传输端点。将此参数设置为逗号分隔的 BCP 47 语言标识符列表,对应于 Twitter 的高级搜索页面上列出的任何语言,将只返回被检测为以指定语言编写的推文。例如,使用 language = en, 连接将仅流传输被检测为英语的推文。
其他语言代码示例:
–es:西班牙语
–pt:葡萄牙语
跟随
逗号分隔的用户 id 列表,表示哪些用户的 Tweets 应该在流上发送。不支持跟踪受保护的用户。对于每个指定的用户,该流将包含:
–用户创建的推文。
–用户转发的推文。
–回复用户创建的任何推文。
–用户创建的任何推文的转发。
–手动回复,无需按下回复按钮即可创建(如“@twitterapi 我同意”)。
位置
一个逗号分隔的经度,纬度对列表,指定一组过滤推文的边界框。只有落在请求的边界框内的地理定位的推文才会被包括在内——与搜索 API 不同,用户的位置字段不用于过滤推文。每个边界框应该被指定为一对经度和纬度对,首先是边界框的西南角。例如:
-122.75,36.8,-121.75,37.8 == >旧金山
-74,40,-73,41。== >纽约市
将创建一个函数来轻松处理“监听”过程中可能出现的任何错误。其中一个参数是我们必须保持窗口打开的时间(秒)。该功能会自动将捕获的推文保存在一个. csv 类型的文件中,以供后期数据分析。
bellow 函数的灵感来源于原始代码,位于: https://stack overflow . com/questions/38281076/tweepy-streamlistener-to-CSV
def twitter_stream_listener(file_name,
filter_track,
follow=None,
locations=None,
languages=None,
time_limit=20):
class CustomStreamListener(tweepy.StreamListener):
def __init__(self, time_limit):
self.start_time = time.time()
self.limit = time_limit
# self.saveFile = open('abcd.json', 'a')
super(CustomStreamListener, self).__init__() def on_status(self, status):
if (time.time() - self.start_time) < self.limit:
print(".", end="")
# Writing status data
with open(file_name, 'a') as f:
writer = csv.writer(f)
writer.writerow([
status.author.screen_name, status.created_at,
status.text
])
else:
print("\n\n[INFO] Closing file and ending streaming")
return False def on_error(self, status_code):
if status_code == 420:
print('Encountered error code 420\. Disconnecting the stream')
# returning False in on_data disconnects the stream
return False
else:
print('Encountered error with status code: {}'.format(
status_code))
return True # Don't kill the stream def on_timeout(self):
print('Timeout...')
return True # Don't kill the stream # Writing csv titles
print(
'\n[INFO] Open file: [{}] and starting {} seconds of streaming for {}\n'
.format(file_name, time_limit, filter_track))
with open(file_name, 'w') as f:
writer = csv.writer(f)
writer.writerow(['author', 'date', 'text']) streamingAPI = tweepy.streaming.Stream(
auth, CustomStreamListener(time_limit=time_limit))
streamingAPI.filter(
track=filter_track,
follow=follow,
locations=locations,
languages=languages,
)
f.close()
让我们测试一下这个功能,听听人们在这个确切的时刻发出的关于特朗普和他著名的墙的所有推文!
每条推文都是一个印在 Jupyter 笔记本上的“点”,这有助于查看“听众是否活跃并捕捉到了推文”。生成了一个文件(tweets_trump_wall.csv ),保存在笔记本所在的同一个目录下。
在这 60 秒的窗口时间内,许多推文被捕获。这是有意义的,因为我们不限制语言或地点。
用熊猫分析推特数据集
我可以说这里的工作差不多完成了。我们现在有了一个数据集。csv 格式,其中实时推文被捕获。现在,让我们使用我们的老熊猫来(几乎)实时读取文件,并继续数据集清理和探索阶段!
在 60 秒内,有 2576 条推文被捕获。数据集有 3 列,一列是作者,一列是日期,第三列是 tweet 文本。在本教程中,我们将只对最后一个感兴趣,但是对于更复杂的分析(如网络科学),手头上有所有 3 个信息是很有趣的。
在一个网络科学项目中,将包含发件人回复的 id(RT @ XXX:)的推文的初始部分分离出来也是很有趣的。这里我们将清除它。
正如我们之前所做的,首先要做的是清理数据集,使用之前创建的相同函数:
现在我们将生成一个新的列,在那里我们将存储每条单独推文的情感分析。
让我们来看看它的单词云:
探索积极和消极的推文
在这一点上,我们可以过滤推文,将它们分为正面和负面,做任何我们认为有趣的分析。例如,每组推文的单词云是什么?
收集标签
我们将执行的最后一个分析将会查看在每种情况下生成的标签。为此,我们将在本教程中使用 Prateek Joshi 开发的函数:使用数据集和代码进行 Twitter 情感分析的综合实践指南。我建议访问他的网站。我从普拉蒂克那里学到了很多。
def hashtag_extract(x):
hashtags = []
# Loop over the words in the tweet
for i in x:
ht = re.findall(r"#(\w+)", i)
hashtags.append(ht)
return hashtags# extracting hashtags from positive tweetsHT_positive = hashtag_extract(df_tws['text'][df_tws['sent'] == 1])# extracting hashtags from negative tweets
HT_negative = hashtag_extract(df_tws['text'][df_tws['sent'] == -1])# unnesting list
HT_positive = sum(HT_positive,[])
HT_negative = sum(HT_negative,[])
结论
就这些了,伙计们!
希望您对数据科学的奇妙世界有了更多的了解!
在我们结束之前,我要感谢 Claudio Aracena 教授,他是我在智利 UDD 大学的数据科学硕士,是他启发了我写这篇文章。
和往常一样,你可以在我的数据仓库上找到 Jupyter 笔记本: Git_Hub 。
来自世界南部的 Saludos!
在我的下一篇文章中再见!
马塞洛
AlphaZero 实现和教程
使用自定义 TensorFlow 操作和自定义 Python C 模块实现 AlphaZero 的演练
**注意(2020 年 1 月 27 日)😗*我已经发布了这个故事的更新,在那里我获得了性能更好的模型。你也可以看我对战改进后的网络。
我在这里描述了我在 Github 上实现的 AlphaZero 算法,它是用 Python 编写的,带有自定义 Tensorflow GPU 操作和一些用于树搜索的 C 语言辅助函数。
AlphaZero 算法经历了三次主要的迭代,先是叫做 AlphaGo ,然后改进到不使用任何预训练叫做 AlphaGo Zero ,最后进一步推广到其他游戏叫做 AlphaZero 。我的实现与 AlphaZero 最相似,然而,所有的变体都相对相似。我为游戏 Reversi 实现了类似的 GPU 功能,但目前我只有 Go GPU 代码可用(如果有人感兴趣,我可以提供前者)。
我写这篇文章的动机是使用相对最少的硬件(单个 GPU)在其他棋盘游戏上使用该算法,因此,我没有试图逐字实现该算法(例如,所有游戏都以预设的回合数进行,而不是使用自动程序在游戏结束时自动重新分配算法)。
我在公共领域发布这些代码、描述和所有其他材料。如果你觉得有用,我很乐意听听。如果你在理解我的代码时有任何问题,我也很乐意尽我所能,在时间允许的范围内提供帮助。我的电子邮件是 X@Y.com。其中 X 是 cody2007,Y 是 protonmail.com。
网络有多好?没那么好——我基本上一直都能打败它,而且我完全是个围棋新手。然而,这种实现似乎确实有效——在训练期间,它对 GNU Go 的分数确实稳步上升(对于使用简单评分系统的 20 回合游戏,网络在大约 50%的时间里开始击败 GNU Go,并且它至少可以在大约 100%的时间里击败完全随机的玩家。
不过有一点是,滥用我使用的评分系统需要走捷径。我通过计算每个玩家在棋盘上的所有石头加上每个玩家完全包围的所有空位来给游戏打分。出于这个原因,神经网络似乎喜欢(尤其是在游戏接近尾声时)将石头放在由另一个玩家安全控制的区域。这大大降低了其他玩家的分数(除了边界上的石头数量之外,其他玩家将不再获得该领土的任何分数),即使该策略在游戏进行超过 20 轮后将不再有效。参见下面关于提高性能的部分。
目录
编译和运行代码
本节假设您已经安装了 Python 2.7、Tensorflow、Nvidia toolkit、GNU C 编译器(GCC)和标准开发环境,以及一些基本的 Python 包:numpy、scipy、colorama。安装和配置一个 Ipython (Jupyter)笔记本服务器来可视化培训进度也是有用的(在下面的中有更多的描述)。如果你想玩网络游戏,你可能还想安装 Pygame Python 包(在下面的中会有更多的描述)。
所有代码都已经在 Ubuntu 18.04 上使用 Python 2.7、Tensorflow v1.7.0 进行了测试和编写,并使用 NVCC v 9 . 0 . 176(Nvidia Cuda 编译器)进行了编译。我在一个 Nvidia Titan X 卡上运行和测试所有代码,该卡配有四核英特尔 i5–6600k CPU @ 3.50 GHz 和 16 Gb RAM。我还没有在其他配置上测试过它,但我怀疑它可能会在不同版本的 Cuda 工具包和 Tensorflow 上编译和运行。
编译代码
编译包括编译 Tensorflow 操作和 Python C 模块。我已经将编译命令写入脚本 build.sh 。因此,为了进行编译,您应该能够将目录更改为您下载代码的位置,并简单地运行它:
cd ~/alpha_go_zero_implementation ./build.sh
如果你有一个比我旧的 Nvidia GPU 卡(或者即使你有一个新的并且想要更好的性能),你需要调整 build.sh 脚本中的-arch=sm_52
标志来匹配你的卡的计算能力。你可以找到你的卡的计算能力,可能有几种方法,但是 Tensorflow 可以很容易地告诉你——在 Python 中你可以运行:
import tensorflow as tf
sess = tf.InteractiveSession()
这生成了相当多的文本,但是如果您看一下最后一行,应该是这样的:
Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 11435 MB memory) -> physical GPU (device: 0, name: GeForce GTX TITAN X, pci bus id: 0000:01:00.0, ***compute capability: 5.2***)
(请注意上面的计算容量陈述,因此“sm_52”是我的最佳选择)。
调整代码的内存需求(如果需要)
当前配置的代码需要大约 7.8 Gb 的 RAM(几乎所有这些都来自树搜索缓冲区)。然而,可以通过调整 py_util/includes.h 中的常量TREE_BUFFER_SZ
和MV_BUFFER_SZ
来缩减这种内存使用(代价是需要N_SIM
,控制用于构建树的每回合模拟游戏的数量,在 bp_tree.py 中,由于树缓冲区的大小减小,也将减少)。
运行代码
有时,树搜索超过了缓冲区大小,这时代码应该检测到这一点并终止。出于这个原因,我创建了一个 shell 脚本,名为 run.sh 来循环运行主训练脚本BP _ tree . py——如果训练终止,它将重新启动它。因此,在命令行中,您可以将当前目录更改为下载代码的位置,并执行 run.sh 。例如:
cd ~/alpha_go_zero_implementation
由于脚本偶尔会终止,如果您开始训练一个新的模型,您需要小心。 bp_tree.py 通过加载脚本顶部变量**save_nm**
中指定的模型文件来工作。如果**save_nm**
设置为None
,脚本将从零开始。所以,当开始训练一个新的模型时,一旦你开始运行脚本,你应该更新**save_nm**
来指示新的模型文件名。如果不这样做,每次脚本(自动)重新启动时,它都将从头开始。理论上,这可以通过修改代码来从树缓冲区溢出中恢复,而不是简单地终止来解决,但是我还没有发现这种麻烦足以证明实现这种行为的时间是正确的。
虽然没有必要,但我经常在一个 GNU Screen 会话中运行所有这些,这样我就可以关闭我的控制台,让代码继续运行(使用screen -r
重新连接)。
可视化算法训练
我已经包含了一个 Jupyter 笔记本,用于可视化网络训练进度,并绘制神经网络玩的示例游戏(参见文件notebooks/training _ visualizations . ipynb)。下面我展示了笔记本中的输出示例。
Black = neural network; white = opponent
Black = neural network; white = opponent
播放训练好的网络
脚本 play_network_gui.py 允许你使用 PyGame 通过一个简单的 gui 来玩网络。将脚本中的变量**save_nm**
设置为模型文件的名称。你可以在两种模式下玩网络游戏:只运行网络(最快,没有树搜索),或者用树搜索。将**run_net**
设置为True
可以让你在没有树形搜索的情况下玩游戏,设置为False
可以启用树形搜索。
算法概述
对算法最好的描述直接来自原文( AlphaGo 、 AlphaGo Zero 、 Alpha Zero )不过,我再简单总结一下。这个想法是使用由神经网络引导的树搜索,该神经网络被训练来估计可能的好棋。树搜索是一种简单的蛮力方法,在每一步棋中,从当前位置到最后一局都要进行多次博弈,以确定某一步棋有多好。树搜索基于神经网络的输出选择移动,并且随着神经网络被训练,树搜索变得更加专注于探索成功的策略。神经网络是如何训练的?它被训练来估计来自树搜索的一组汇总统计(树搜索访问每个移动的频率,以及每个移动导致网络赢得游戏的可能性)。在训练过程中,网络与自己对抗,不需要人工训练。
代码概述
我已经将控制围棋规则和运动的所有代码放在 tensorflow GPU 操作中(在子目录 kernels/ 中)。我保存在 CPU 上的树结构(在子目录 py_util/ 中)。我以前也将树搜索实现为 tensflow GPU 操作,但出于两个原因,我决定将其转移到 CPU。首先,当运行超过 1000 次模拟的树搜索时,我开始遇到 GPU 内存限制。第二,我发现在 CPU 上保留树搜索代码可以提高大约 2 倍的速度(尽管我没有在优化 GPU 树搜索实现上投入太多时间)。
GPU 游戏数据结构
所有 GPU 数据结构都在 row-major-order 中,并在 cuda_includes.h 中定义。
板子以尺寸[BATCH_SZ, MAP_SZ_X, MAP_SZ_Y]
储存。
**board**
代表当前正在评估的游戏板。**board2**
代表在进行树搜索评估时使用的游戏备份(备份和恢复棋盘的代码在kernels/session _ backup . Cu . cc中)**board_prev**
和**board_pprev**
代表当前前一圈和两圈的棋盘,以防止重复移动(同样**board_prev2**
和**board_pprev2**
是类似于**board2**
的备份)**n_captures**
和**n_captures2**
(形状[N_PLAYERS, BATCH_SZ]
)表示每个玩家在当前游戏中捕获的次数。这仅用于统计,算法实际上并不需要。**ai_to_coord**
(形状[BATCH_SZ]
)是随机播放器选择的坐标,也是算法不需要的(它是内核/move_random_ai.cu 的输出,并输入到内核/move_unit.cu )**valid_mv_map_internal**
(形状[BATCH_SZ, MAP_SZ]
)这是每个棋盘的二进制图,表示哪些位置是有效移动(1),哪些不是(0)。它是内核/create_batch.cu 的输出,由内核/move_unit.cu 使用,以确保只进行有效的移动。
CPU 树数据结构
所有 CPU 树数据结构(在py _ util/_ py _ util . cPython 模块中使用)也在 row-major-order 中。它们在 py_util/includes.h 中定义,并预先分配为固定大小,以避免需要不断分配和释放内存。必须注意不要超过缓冲区的大小(否则代码会终止)。
节点数据结构:
每个节点代表一个棋盘状态,并包含一个指针,指向一个列表:有效的棋步、它们的访问计数、平均 Q 值和由神经网络分配的获胜概率。这些值(访问计数和平均 Q 值)在网络每次移动时都会更新——所有导致当前状态的游戏状态都通过 py_util/backup_visit.c 函数进行更新。
**tree_sz**
(形状[BATCH_SZ]
)表示节点数(有子节点)。它不应该超过TREE_BUFFER_SZ
,否则我们就有一个内存溢出。**tree_start**
(shape[BATCH_SZ]
)是代表当前板卡状态的节点的索引(应该总是> = 0 且小于 tree_sz)。**tree_player**
(形状[BATCH_SZ, TREE_BUFFER_SZ]
)代表每个节点下一个移动的玩家。**tree_parent**
(形状[BATCH_SZ, TREE_BUFFER_SZ]
)表示每个节点的父节点索引。值-1 表示我们在树的根部。**tree_list_sz**
(形状[BATCH_SZ, TREE_BUFFER_SZ]
)表示对于每个节点(游戏状态),可用的子节点(可能的移动)的数量。对于所有已初始化的节点,该值将始终至少为 1(表示无移动),并且永远不会超过1 + MAP_SZ
(有效移动不可能超过电路板尺寸)。**tree_list_start**
(形状[BATCH_SZ, TREE_BUFFER_SZ]
)表示,对于每个节点,我们可以在其中找到孩子和他们的统计数据(访问计数,Q 值的总和)的索引。这不应该超过MV_BUFFER_SZ
,否则我们会有一个内存溢出。
列表数据结构:
如上所述,每个游戏状态(我称之为节点)都与一个有效移动、访问计数、平均 Q 值和概率的列表相关联。这些结构每个都存储在一个大的缓冲区中,节点数据结构包含一个指针(tree_list_start
)在这个缓冲区中指向一组连续的条目(tree_list_sz
表示条目的数量)。
**list_sz**
(shapeBATCH_SZ
)包含列表中活动元素的数量。随着列表的增长,元素被添加到末尾,每个游戏的**list_sz**
条目也相应地增加。对于任何给定的游戏,它都不应该超过MV_BUFFER_SZ
,因为这是下面描述的列表缓冲区的大小。**list_valid_mv_inds**
(shape[BATCH_SZ, MV_BUFFER_SZ]
)对于每场游戏,它包含下一个移动的玩家的有效移动指数。**list_valid_tree_inds**
(shape[BATCH_SZ, MV_BUFFER_SZ]
)对于每个游戏,它包含有效的节点索引(这是一个指向上述结构的指针——变量以**tree_**
开始)。有了节点索引,就可以从**tree_list_start**
索引中查找下一组叶子。**list_valid_tree_inds**
如果未初始化,将为-1。当网络移动到一个未初始化的节点时,它将被 py_util/register_mv.c 函数初始化。**list_q_total**
(形状[BATCH_SZ, MV_BUFFER_SZ]
)对于每一个游戏,它代表了游戏中这里和向前访问的所有状态的 Q 值的总和。除以访问次数得到平均值。**list_prob**
(形状[BATCH_SZ, MV_BUFFER_SZ]
对于每一场比赛,都包含了网络对比赛获胜概率的预估。该值只需设置一次(与访问计数和平均 Q 值不同),并在 py_util/choose_moves.c 中设置。**list_visit_count**
(形状[BATCH_SZ, MV_BUFFER_SZ]
)对于每一局游戏,它包含这步棋及其后的每一步棋的次数。
模型架构
我的所有测试都集中在一个 5 层残差的卷积神经网络上,每层有 128 个滤波器(尽管这些参数可以通过改变 bp_tree.py 很容易地改变)。这是一个比 AlphaGo 论文中报告的网络更小的网络,并且受到我自己的硬件和时间限制的影响。
培训脚本的演练
这里有一个训练脚本的演练, bp_tree.py 。通常,它设置并加载通用变量和状态信息。然后进入主循环,首先通过自玩生成训练批次,然后除了随机反射和旋转棋盘之外,还会以随机洗牌的顺序对其进行训练。在循环结束时,它将保存并评估网络,然后重复循环。
脚本的第一部分只是导入模块,并设置加载和保存神经网络模型的目录。值得一提的两个文件是 global_vars.py 文件,它包含例如批量大小,以及architectures/tree _ TF _ op . py文件,它使用将在本脚本稍后定义的参数在 Tensorflow 中构建神经网络。
接下来,我们设置是否希望脚本开始训练一个新的网络,或者加载并恢复训练一个现有的网络(通过设置**save_nm**
变量)。此外,我们还创建了一个希望保存在模型文件中的变量列表,以及将用于记录性能的变量。
现在我们要么初始化一个新模型,要么加载它。这是您更改模型或训练参数的地方,例如层数、过滤器、损失函数权重或梯度下降步长。同样,如果你想减小梯度下降步长,你可以在if
语句的else
分支中设置EPS
变量。
接下来是保存和恢复当前树位置的函数(为简洁起见,此处省略),以及一个用于生成输入 Tensorflow 的字典的简单函数。之后,是执行树搜索的主函数。首先备份当前树状态:
然后,我们进入主要的模拟和轮流循环,每个玩家都要走一步。然后,我们向前运行神经网络,以获得它对每一步都会导致它赢得游戏的概率的估计(脚本中的**pol**
变量)。我们还得到它对当前游戏状态的 Q 值估计(脚本中的**val**
变量),以及当前玩家可以进行的有效移动的列表(由内核/create_batch.cu 函数生成)。如果我们不在模拟的第一步,那么脚本会将 Q 值备份到导致当前状态的所有游戏状态。
有了有效移动的列表,我们就可以通过调用来自上面编译的 Python C 模块的pu.add_valid_mvs
函数来更新树。接下来,查询树以基于网络概率和 Q 值估计找到下一步。然后,我们在树中注册移动,然后进行移动(这将更新包含板状态的 GPU 内存结构,以下面的行开始:arch.sess.run
最终调用内核/move_unit.cu 函数)。
最后,在每次模拟结束时,我们必须备份谁赢得了每场比赛(网络正在玩自己,但这是必要的,以了解哪一套动作赢得了训练)。我们还恢复了之前在树中的位置。
接下来是一些额外的初始化代码(这里没有显示)。之后,是主要的训练循环。首先我们调用kernels/init _ state . Cutensor flow 操作,它在 GPU 上初始化一组新的游戏板。我们还通过调用 py_util/init_tree.c 来初始化树数据结构。接下来,对于每个回合,我们运行模拟代码来播放N_SIM
(例如。1000)游戏。然后,我们为每个玩家保存棋盘状态(稍后用作训练输入),并获得有效走法的列表。
和以前一样,使用run_sim
函数,我们在树中注册有效的移动,然后使用树来选择当前玩家的下一步移动。然而,这一次不是选择具有最大树搜索值(反映了访问计数、Q 值和网络概率估计)的移动,而是在概率上与树搜索访问每个移动的次数成比例地移动。然后,像以前一样,我们在树中注册移动。最后,我们修剪树( py_util/prune_tree.c ),这意味着我们移除所有不再可访问的节点和叶子(那些表示从当前移动中不再可能的分支)。
现在,为了构建我们的训练批次,我们确定谁赢得了每场比赛(我们训练网络来预测它——Q 值,或脚本中的**val**
变量),并确定树搜索将为给定的比赛状态选择每一步的概率(我们也训练网络来预测它——脚本中的**pol**
变量)。py _ util/return _ probs _ map . c函数从我们之前的模拟中返回每个回合、玩家、游戏和移动的概率。它的尺寸是:[N_TURNS, N_PLAYERS, BATCH_SZ, MAP_SZ_X, MAP_SZ_Y]
。
最后,我们准备训练网络。我们打乱了训练步骤的顺序,然后旋转和反射棋盘,这给了网络更多种类的输入进行训练,并防止它过度适应任何一个游戏。
然后,我们执行实际的反向传播步骤。对于诊断,我们加载损失函数值来监控训练进度。这些值随后在临时变量中递增,并在脚本中打印、保存和重置。
脚本的其余部分(这里没有显示)将保存模型,并根据随机对手和 GNU Go 算法对其进行评估。该代码中还包含打印语句,用于查看一些损失函数输出。
训练速度
在我的设置中(硬件描述见页面顶部),生成 128 个游戏玩 20 回合,每回合 1000 次模拟,大约需要 40 分钟。我的代码随机旋转并反映棋盘,并在所有回合的每次移动中进行训练(因此在 40 分钟内,它生成 20 个训练批次)。我在这一页上展示的图代表了大约一个月的训练[ (25000 训练步* (40 分钟/ 20 训练步))/(60 分钟/小时* 24 小时/天)] = 34 天。
进一步提高性能
我注意到当游戏模拟的数量从 500 增加到 1000 时,性能有所提高(见下文;蓝色= 1000 次模拟;红色= 500 次模拟)。请注意,相对于 GNU Go 算法,胜率略有增加。我怀疑模拟的额外增加也可能提高性能,但我没有测试这一点。奇怪的是, AlphaZero 的论文只使用了 800 次模拟,然而,还有其他可能让他们获得更好性能的差异,如超参数调整(我没有系统地完成),以及增加网络规模。在我对游戏 Reversi 进行的一些旧测试中,我注意到随着模拟数量的增加,性能有了更显著的提高,而随着网络规模(层数和过滤器数量)的增加,性能几乎没有提高。然而,这些结果可能不适用于围棋,因为状态空间和每回合的移动次数都在急剧增加。如果其他人对此有想法,我会很高兴听到(我的电子邮件在页面顶部)。
比较 1000 次与 500 次模拟
blue = 1000 simulations; red = 500 simulations
(Darker colors are games played against GNU Go, lighter colors are against an opponent making random moves)
另外一个我认为会提高性能的变化与评分系统有关(基于我在 Computer Go 邮件列表中的讨论)。正如文章开头提到的,网络可以通过在其他玩家“安全”控制的区域放置石头来增加分数。解决这个问题需要:实现一个辞职机制(目前,所有的游戏都是每个玩家转 20 次),并增加每个玩家可以转的次数,以便可以在安全控制的区域内捕获流散的石头。因为我用的是小棋盘(7x7 ),如果回合数增加,放弃机制可能会很关键,因为否则玩家可能会用完所有的棋步,被迫开始填充自己的领地,使他们容易被俘获。
我计划实施辞职机制,并在某个时候增加回合数,并将尝试在此链接到我的结果。
替代“事实”与替代“推论”
假设你在三次抛硬币中看到了以下序列:
HHH
以下哪个陈述是“事实”?
- 硬币是固定的,总是“正面”落地。
- 硬币是公平的。
简而言之,这两种说法都不构成“事实”。它们都是推论,取决于两件事:观察者对宇宙的先验知识和实际的无可争议的事实——你已经连续得到了三个头。我们可能或可能不相信硬币实际上是公平的,或多或少,你只是有一系列的机会发生,这不是不可能的(1/8)。因为我们不认为,先验地,一个硬币总是“正面”落地是非常可能的,如果有可能的话,我们(我认为是正确的)认为这是非常可能的。但是,在实际的“事实”中,没有什么是无可争议的,这使得“边缘”认为硬币是固定的不太可能。如果有什么不同的话,它实际上加强了这种信念。
出于各种原因,我们可能认为这枚硬币接近公平,如果不是公平的话。我们可能会认为,即使真的有可能,魔法硬币的存在也不太可能。我们以前可能见过很多次扔硬币,即使不一定是来自这枚硬币,并且相信(并非不可能)这是一枚硬币,就像任何其他硬币一样(即使我们没有明确或知情的理由相信这种或那种方式)。或者,我们可能很懒,认为 50-50 的几率是合理的。实际上,这些都没有说有问题的特定硬币不是固定的。相信这枚硬币是固定不变的“阴谋论者”没有理由相信他的信念是错误的,因为事实是无可争议的。唯一能说服他的“事实”是,在某次投掷中,硬币出现了“尾巴”。换句话说,要证明阴谋论者不是这样,你需要投资一个实际的实验,在这个实验中,你愿意支付一笔费用来证明,在一些 N 次投掷中,其中 N>3,至少有一个“尾巴”。(或者,如果阴谋论者有一个更微妙的信念,即真正的 P(H) = .9 或其他什么,这个结果足以刺激他的 P 值。)
底线是“另类推论”很容易,显然很常见,相当自然,事实上经常有无可争议的真实事实支撑。我们经常认为其他人是错误的,因为他们从我们的同一组事实中得出不同的推论,但这仅仅意味着他们是带着不同的先验得出的观察结果,通过这些先验来评估事实。
渐近地,如果我们接触到来自相同分布的相同数据流,也就是说,当观察值接近无穷大时,所有的信念都会收敛。但是,我们真的还会从同一个分布中汲取能量吗?数据的供应,尤其是面对总体上太多的数据,一直在转变以满足我们的过滤器。我指的不仅仅是关于政治的新闻:我们不成比例地从我们容易获得并且出于某种原因感兴趣的数据中提取信息——在我处理营销相关数据的少数场合,我觉得有趣的是,我所拥有的样本的人口统计数据严重倾向于更年轻、更富有和更懂技术的人。如果你想知道是什么让这群大概具有经济价值的人群的 p 值发痒,我想这没问题。但是如果你想知道你的产品在这个群体之外的销售情况,这将是一个糟糕的例子。因此,我们对大量数据的子集进行采样,以进一步探索,因为它们符合我们的先验(和成本考虑),因此继续循环。而且,说实话,数据消费者主动花更多的钱去获取他们不知道该怎么做的数据是愚蠢的,因为有大量廉价的数据可供他们使用。(在政治心理学中,我们称这种酒鬼的寻找,我其实远比不同情故事中的酒鬼。)但底线是,鉴于抽样过程中的偏差,信念的趋同将非常缓慢或不存在。这才是真正的问题。(这还是在我们提出样本量接近无穷大之前。)观察不同的观察者有不同的数据样本,并且他们拥有的样本在不同程度上与不同的信念兼容,这没有什么“可替代性”,从而导致不同的推论。明智的数据分析用户应该认识到,这是采样过程和观察者带来的不同先验中(几乎是固有的)偏差的自然副产品。然而,即使在认识到可变性的同时,承认“事实”及其限度也不应该是一个太大的问题:HHH 已经发生是无可争议的,并且由 P(H) = 1/2 和 P(H) = 1 的先验产生的概率是可以计算的。不同观察者之间的争议在于概率 P(P(H) = 1),而目前可用的“事实”对解决这个问题贡献甚少——只有收集额外的事实才能解决。如果得不到事实,我认为,除了承认存在差异并就哪些额外数据(如果能够找到的话)将使解决办法朝一个方向或另一个方向发展达成一致之外,没有什么意义。(这就是实验(也称为 A/B 测试)的用武之地:有时,你可能拥有的绝大多数大数据与实际争议无关,实际相关的子集可能太小,无法产生足够的统计功效。唯一的解决办法是人为地收集这个未被充分代表的子集的更多数据。)
我不认为“替代事实”真的存在:首先,很少有无可争议的事实独立于解释而存在。我们想到的大多数“事实”实际上是推论,通常非常可靠和稳健,但仍然是从有限的事实中推断出来的推论。我们没有人真正到过月球,除了少数参与阿波罗计划的人——如果它真的发生了(哈!开玩笑的。)我们“知道”登月发生了,因为有许多间接的线索表明它是真实的,而且让我们感到荒谬的是,这样的事情可能是伪造的。但这些存在于“推论”的领域,而不是事实,它们反映了我们是什么和谁,而不是我们“知道什么”。真正困扰我们的是,有些人,也许甚至许多人,并没有得出同样的推论。
我并不是说这个问题无关紧要。远非如此,问题是“替代性推论”而非“替代性事实”表明了一个更深层的问题。如果这仅仅是一个“事实”的问题,很容易证明相反的事实是无可争议的,这将是它的结束。在上面的例子中,硬币是固定的这一信念将被一个单一的结果打破,在这个结果中硬币落在“尾巴”上。如果硬币是公平的,它将很快发生。然而,另一种推论建立在更加“复杂”的基础上,既有“理论的”,也有“经验的”,从这个意义上说,它们包含了关于宇宙如何运作和产生数据的完整理论,以及足够支持这些“理论的”基础的数据。换句话说,一套完整的“替代心智模型”是存在的。如果人类的社会性是建立在对我们周围世界的共同信念之上的,即使没有明确地陈述事情,我们也知道什么构成了道德的、适当的和“正常的”行为和互动方式,并相应地遵循,这种共同思维的缺乏表明他们没有不同推论的持有者所共享的共同“人类社区”。换句话说,“替代性推论”与其说表明它们“知道”不同的东西,不如说表明它们是不同的。
仅仅告诉“他们”他们的“事实”是错误的是荒谬的——这相当于告诉他们 HHH 的意思是 P(H)= 0 . 5 左右。是的,HHH 和 P(H) = .5 远非不相容——P(HHH | P(H)= . 5)实际上相当高。但是 HHH 更适合 P(H) = 1。如果我们试图说服某个相信 P(H) = 1 的人,我们应该多扔几次硬币。是的,我们三次投掷就赢了 HHH。如果我们有 N= 10,你会期待什么?请注意,我们不一定要把我们的信念强加于他人——我们实际上可能被证明是错误的,我们可能会得到 10 个 H,无论这可能多么不可思议,如果是这样,阴谋论者可能有一个观点,但如果是这样,它将基于一个更坚实的经验主义基础,无论如何。但是,最有可能的是,这不会发生,而且除了掷十次硬币之外,没有其他替代的事实。当然,这意味着我们实际上必须付费来运行实验以收集我们需要的数据,而不是试图从我们拥有的有限数据中挤出推论(即使是大数据在有偏差的采样面前也是有限的,所有数据在某种程度上都是有偏差的——唯一的问题是,它们的偏差是否如此之大,以至于我们应该对它们持怀疑态度。)
用网络科学进行客户分析
用于客户细分的用户评论网络
在过去的一二十年里,美国人继续偏爱可追踪的支付方式,这为零售商和供应商提供了丰富的客户数据来源。数据科学家使用这些数据来帮助企业在库存、营销和供应链等方面做出更明智的决策。有几种工具和技术可以进行客户细分,网络分析可能是一种强有力的工具。
在之前的文章中,我们看到了复杂网络分析在社交媒体僵尸检测中的效用。在这里,我们将看到网络科学的工具是如何应用于客户分析的,仅以 Amazon.com 产品评论的两个元数据为例。我们将首先提供关于数据集的信息,以及网络是如何创建的。然后,我们将深入到以自我为中心和全局指标的网络分析中。然后我们将解释这些信息告诉我们什么,最后从商业角度推测它可能意味着什么。
关于数据集
这项工作中使用的数据集是由加州大学圣地亚哥分校的 Julian McAuley 博士提供的。它“包含来自亚马逊的产品评论和元数据,包括 1996 年 5 月至 2014 年 7 月的 1.428 亿条评论”。产品评论数据集包含用户 ID、产品 ID、评级、有用性投票和每个评论的评论文本。
这项工作中建立的网络来自健康和个人护理评论 5 核数据集,这意味着它只包含至少有 5 个评论的用户和产品。在 2014 年 1 月至 7 月期间,进一步对数据集进行了抽样审查。总而言之,这个采样数据集包含超过 25,000 个要分析的用户帐户,每个帐户至少有 5 条评论。
关于网络结构
Figure 1. A sample from the constructed network showing two connected cliques. All nodes represent accounts that have reviewed some product. Thus, all nodes that reviewed a given product form a clique, and any given node that reviewed multiple products will connect multiple cliques.
数据集中的每个用户帐户由网络表示中的一个节点来表示。如果节点的帐户共享已审核的产品,则节点由边连接。*在该图中,网络由针对每种产品的集团的集合组成。产品集团将由作为两个集团成员的节点连接(审查多个产品)。图 1 示出了网络中的一个集团的例子。
自我中心网络分析
分析从计算网络中每个节点的几个关键自我中心度量**开始(自我中心的意思是以该节点为中心):度-目标节点共享一条边的节点数量,中心性-网络中节点的相对重要性(在这项工作中,使用了特征向量 Katz 和中间中心性),以及聚类系数-目标节点邻域中可能存在的[三元组](http://Figure 1. A sample from the constructed network showing two connected cliques. All nodes represent accounts that have reviewed some product, and as a result, are all connected.)的比例。
每个节点的这些计算指标在下面的图 2 中相互对照。
Figure 2. A pairwise plot of clustering coefficient, degree, eigenvector centrality and Katz centrality of each node in the review network. The plots in the diagonal are the probability density functions of the metric.
值得注意的是,在大多数样地中,存在由低密度区域分隔的两个不同的集群。在运行聚类算法将节点分成两类:绿色(24,201 个节点)和蓝色(821 个节点)之后,Katz 中心性与聚类系数的关系图如图 3 所示。
Figure 3. Two clusters identified with the DBSCAN clustering algorithm.
在节点被识别并标记为“绿色”或“蓝色”后,指标将被重新绘制,如图 4 所示。Katz 中心性与聚类图中的节点类被分成在其他成对图中看到的相同的不同聚类。这证实了导致聚集现象的机制对于每个度量对都是一致的。也就是说,一个图中的一个集群中的节点的类成员关系依赖于它在其他图中的成员关系。
Figure 4. Pairwise plots with cluster membership. Clockwise from top-left: Katz vs eigenvector centrality, log(degree) vs log(betweenness centrality), clustering coefficient vs degree, Katz centrality vs degree.
一个特别值得注意的观察是 Katz 与特征向量中心性图中的聚类。Katz 中心性的计算方式与特征向量中心性几乎相同,区别在于节点最初被赋予“自由”中心性。该图表明,与具有可比特征向量中心性的绿色节点相比,该自由初始中心性更多地提高了蓝色节点的 Katz 中心性。
有两个网络属性可以解释这一点。首先,“蓝色”节点的度数往往比“绿色”节点的度数高得多(如图 4 中包含度数的图所示)。第二是“蓝色”节点的平均聚类系数是 0.74,而“绿色”节点的平均聚类系数是 0.59。一般来说,度数大的节点往往比度数小的节点具有更小的聚类系数,因为完成相同比例的三元组需要更多的连通性(这与度数的阶乘有关)。“蓝色”节点具有更高的度和更高的平均聚类系数,这一事实表明它们比“绿色”节点位于网络中更密集的区域。因此,“蓝色”节点的 Katz 中心性计算的自由初始中心性增加是复合的。
网络协调性
识别出两个不同的节点类别后,确定网络的分类性是有意义的。也就是说,蓝色节点往往不与绿色节点连接(同配混合,如图 5 所示),还是它们很好地混合在一起(异配混合)?
Figure 5. An assortively mixed network. Credit: M. J. Newman
这可以用模块化值来回答:连接相同类型节点的边的分数减去随机期望的分数。正模块度值表示分类混合;负值表示异配混合。虽然在理论上,该值被限制在 1 以上,但是大多数网络结构不会随机产生 0 个预期的同质边。考虑到这一点,通常通过假设连接相同类型节点的边的分数为 1 来计算网络结构所允许的最大模块化值。
评论网络的模块化值被发现是 0.302,0.419 是最大可能的模块化值。因此,网络具有很强的分类混合性;也就是说,“蓝色”和“绿色”节点的连接程度明显低于偶然情况下的预期。在产品评论的背景下,这意味着“蓝色”和“绿色”用户在很大程度上是在评论(并且,可能是购买)不同的产品组。
结论
对这一评论网络的分析得出的结论是,有两个客户群对不同的产品群很感兴趣。其中一个客户群比另一个要小得多,但联系更加紧密,这表明它代表了更多的小众利益。历史销售信息将允许确定这个利基群体对收入的贡献。如果它贡献了与其规模不相称的数量,零售商可以通过扩大或定制其服务和产品来增加收入。
*多边是指边的权重
- 边权重被*考虑在度和中心性的计算中
阿姆斯特丹的环境救星:循环经济
对于这座高科技城市来说,节能减排是不够的
原材料是一种有限的资源。随着依赖这些资源的行业数量的增加(如智能手机和平板电脑的金属),消费者的数量也在增加。在未来的几十年里,我们不仅将开始耗尽这些材料,而且其提取和制造过程往往对工人、公民和环境有害。
从长远来看,依赖原材料在经济上是不可行的。随着库存的减少,资源市场变得越来越不稳定。世界上每个国家都交易自己的原材料,所以如果这些环节中的一个环节无法再交付,整个供应链都会受到影响。到 2050 年,荷兰政府希望荷兰完全依靠可再利用的原材料。所以阿姆斯特丹市正在试验一个解决方案:循环经济。
什么是循环经济?
目前的系统是线性的。我们获取资源,制造产品,然后在完成后处理掉它们。循环经济旨在封闭循环,以尽可能保留更多价值的方式对废物进行再利用、再利用或回收。这包括减少制造过程中的热力学熵,并尽可能避免向下循环。如果你用一件旧棉布衬衫作为例子:
重复使用圆圈
- 维修和维护以延长产品的寿命
- 重用和再分发(转售/共享)
- 翻新、翻新和再制造
- 回收部分或全部材料
制造商必须提前考虑,创造易于拆卸和重复使用的产品——所以在这种情况下,考虑纯棉而不是混纺。另一个想法是公司租赁产品而不是出售。所以租赁洗衣机给你的公司会负责维护和修理。然后,当机器达到其寿命的尽头时,他们将收集它并重新使用这些材料,而不是必须挖掘新的材料。随着数据科学、存储能力和机器学习的进步,这些物流系统正开始成为现实。
荷兰银行 ABN·AMRO 刚刚在其总部阿姆斯特丹开设了荷兰的第一座圆形建筑。几乎所有使用的材料都是可回收的,当建筑最终被拆除时,它可以很容易地拆卸,所有材料都可以重复使用。Circl 大楼拥有循环解决方案,如由员工的旧粗斜纹棉布牛仔裤制成的隔热材料、拆除办公楼的窗框和屋顶上的 500 块太阳能电池板。
有什么好处?
环境的
- 通过重复使用物品,从产品转向服务(非物质化),生产的产品会更少。从而减少制造业的排放和污染物
- 任何产生的排放都将被重新利用。二氧化碳可以用来在温室中种植农产品,培养藻类来制造燃料,甚至可以被矿化。冰岛的一个研究小组发现,将二氧化碳溶解在水中,并与包括玄武岩在内的混合物一起注入地球,95%的二氧化碳将在两年内矿化为方解石
- 废品被重新利用。有机/生物废物可用于牲畜饲料、沼气甚至生物塑料
- 仅在荷兰,循环方法就可以减少 8%的二氧化碳排放,减少 2180 千米的土地使用,减少 7 亿立方米的水消耗,减少 25%的原材料进口
商业
- 可持续性优先于利润,因此公司的寿命得以延长
- 循环经济确保优化能源消耗,减少浪费。这两者都有助于提高生产效率,从而节省资金
- 回收材料意味着公司不必购买新的资源,从而避免市场价格波动和库存减少
- 通过采取更环保的方法改善与政府和消费者的关系
经济的
- 将经济从有限的线性系统中分离出来
- 与提取原材料相比,资源节约高达 70%
- 循环系统创新和数据科学中的新经济
- 一个新的劳动密集型的回收和维修就业市场
- 减少生态足迹
- 在荷兰,循环经济可以为政府带来 73 亿€,并创造 54,000 个新的工作岗位
(数据来自艾伦·麦克阿瑟基金会和循环合作)
阿姆斯特丹的合作经济
在我们的智慧城市文章中,我们谈到了城市的协作性质。阿姆斯特丹分享城市的一项研究证实了这一点,84%的居民表现出分享的动机。从产品中获取最大价值是创造循环经济的第一步。由于出色的 ICT 结构,该市 90%的地方都可以上网,因此阿姆斯特丹的初创公司一直在与市政当局合作,创建居民可以相互分享的应用程序和平台。
重新穿上
一个在线社区平台,由我们 Eli5 的创始人设计。用户可以将他们的设计师衣柜出租给其他时尚人士,这样衣服就能得到最大限度的利用。此外,用户可以通过租金来减轻对银行账户的打击。
皮尔比
超过 20%的二氧化碳排放来自耐用消费品。平均而言,这些资产的使用寿命只有 10%。Peerby 是一款为分享这些物品而制作的应用。用户可以拯救环境,节省购买物品的时间,节省购买昂贵物品的费用,并结识许多友好的人。该平台几乎涵盖了你能想到的任何物品。从汽车、自行车和旅行用汽车;到聚会的凉亭、烤肉架和折叠椅。
闭环——将废物转化为材料
显然,共享经济首先没有产品来共享是行不通的。生产这些产品仍然会导致二氧化碳排放、有毒污染和其他形式的废物。阿姆斯特丹经济委员会的目标是通过将废物流与企业联系起来,来结束这一循环。
区域热网
许多制造过程会产生热量,这些热量通常作为废物通过水或空气处理掉。阿姆斯特丹经济委员会已经召集了 32 个政府机构、能源公司和热能生产商来将热能输送到家庭。
在阿姆斯特丹的 Houthaven 区,一所新的学校和住宅楼将使用该网络供暖,并使用 IJ 河的水进行冷却。与使用传统锅炉和空调系统的公寓相比,社区中的每个家庭排放的二氧化碳将减少 80%。
Sanquin 血库已经与 Waternet(阿姆斯特丹自来水公司)建立了联系,在冬季也可以使用饮用水的冷能。这将最初储存为热能,用于 Sanquins 制药过程。他们期望在最初几年提取 20,000 千兆焦耳的能量。人们还期望家庭能够节省金钱和能源,因为这一过程提高了饮用水的温度,用户在家中加热水所需的能源将会减少。
智能电网
智能电网是阿姆斯特丹最初的智能城市计划之一,其重点是储存和回收剩余能源。与欧盟的城市禅项目合作,太阳能电池板被安装在整个城市,以收集全天的能量。然而,一旦他们的电池达到最大容量,就没有更多的电可以储存,宝贵的日光就浪费了。增加产能需要在基础设施上进行昂贵的投资。
在该市组织的黑客马拉松 Appsterdam 上,获胜的解决方案是使用电动汽车电池作为存储。这就产生了一个临时的“虚拟发电厂”,同时政府建造了一个更大的储存库。智能电网还将允许太阳能电池板所有者使用智能电表平台将多余的能量卖回给供应商。所以每个太阳能电池板都被充分利用,总有地方可以储存产生的能量。
信用:阿姆斯特丹智能城市
PUMA —城市采矿
PUMA 是来自莱顿大学、代尔夫特理工大学、Waag 协会和代谢的研究人员的合作项目。该研究项目旨在发展城市矿山的理念,并绘制阿姆斯特丹地质图,以显示某些金属(如铜)的存在。PUMA 团队希望创建一个城市建筑及其材料构成的数据库,因此未来有可能开采这些资源。
鸣谢:Waag 社会生活实验室
别克斯洛特汉姆
作为 2016 年欧洲创新之都奖的获得者,阿姆斯特丹经常被描述为“活实验室”。北面的 Buiksloterham 是以用户为中心的开放式创新生态系统的最佳范例。
Buiksloterham 以前是许多制造企业的所在地,包括一家飞机制造厂、石油实验室和造船厂,在 2008 年经济危机之前一直被指定为私人开发项目。由于无力自行出资建设,该市向希望自建环保住宅的可持续企业和居民开放了该地区。中央车站对面的滨水区现在布满了初创企业、循环企业和新住宅。
德塞韦尔
De Ceuvel 建在一个旧造船厂的旧址上,几乎完全是圆形的。这片土地已经被严重污染,所以选择了在 10 年租期结束时能够自然解毒的植物。
与此同时,办公室工作区建在高架平台上,使用不再使用的升级船屋。还有一个咖啡馆和餐厅,由重新利用的木材和回收船及其他零碎材料制成的户外座椅建成(当夏天到来时)。还有:
- 堆肥厕所,因为受污染的土地阻止挖掘污水系统
- 热交换器,收集 60%离开办公室船只的热量,并进行再循环
- 水生植物过滤器,用于处理废水
- 每年产生 36,000 千瓦时电力的太阳能电池板
- 鸟粪石反应器,从有机废物中制造肥料
- 和一个生产鱼和蔬菜的水培温室
信用: deceuvel.nl
阿姆斯特丹的下一站是哪里?
欧盟、荷兰政府和阿姆斯特丹市政当局都实施了鼓励循环经济创新的计划和激励措施。例如,van Plestik 是一家位于 Buiksloterham 社区的公司。他们创造了一种 3D 打印机,可以使用混合和不纯的塑料将废物升级为家具和其他产品。当你考虑到传统的 3D 打印机必须使用塑料,必须提炼回近乎纯粹的“原始”形式时,这是一个重要的里程碑。在帮助下,这家初创公司在短短几个月内的旅程是这样的:
- 创作者从 2016 年初开始,通过与 3D 打印和城市建设专家 Hogeschool van Amsterdam 和 CRE8 合作,研究用回收塑料打印
- 到那年 5 月,该项目入围了由欧盟主要的气候创新倡议组织举办的气候启动平台竞赛。该计划为他们提供了一个为期 4 天的研讨会流程,专家可以帮助他们加快业务发展
- 2016 年 9 月,他们被选为驻阿姆斯特丹创业计划的成员,并获得了为期 5 个月的每周培训,以发展他们的公司
van Plestik 现在已经建造了他们的第一台工作打印机,并在 2017 年 6 月赢得了“阿姆斯特丹,让你的城市”奖,以进一步发展他们的循环回收项目。
信用: vanplestik.nl
Amsterdam Economic Board 的下一阶段是建立一个循环数据平台,将所有的研究、业务和已经创建的系统结合在一起,类似于智能城市的城市数据平台。其主要目的是存储阿姆斯特丹的资源和垃圾流数据,以确保该城市能够最有效地利用每一种资源。该平台还将成为企业和公民收集和分享数据、讨论可持续发展战略以及为那些希望减少环境影响和实施循环战略的人提供建议的地方。
这方面的一个例子是阿姆斯特丹的一家名为因斯图克的餐厅,这家餐厅使用了原本会被浪费掉的食物。它依赖于 Albert Heijn 超市的废物流,但希望通过该平台找到其他供应商。
循环数据平台的最终目标是为每一种材料提供通行证,这样原材料的供应就不会流失到垃圾填埋场和其他低价值的废物处理方法中。与此同时,阿姆斯特丹市政府将继续举办循环创造挑战等活动,以鼓励持续创新,实现完整的循环经济。
一个不正常的人在寻找一个有趣的数字
拍了下面这张图,从写着“正常”的建筑里,照片里的不正常的家伙——我——想起了一个“有趣”的悖论。
假设我按顺序列出自然数:
- 1
- 2
- 3
- 4
- …
现在让我们说说这些数字中的一些有趣的东西:
- 1 是所有数中的第一个数,它是所有其他数的约数
- 2 是第一个也是唯一的质数
- 3 是第一个古怪的表亲
- 4 是第一个完美的正方形
- …
假设具有有趣性质的数称为“有趣”数。
而不有趣的数字才是“正常”的数字。
使用此定义,列表将如下所示:
- 1 是一个有趣的数字
- 2 是一个有趣的数字
- 3 是一个有趣的数字
- 4 是一个有趣的数字
- …
现在假设数字 x 是列表中的第一个“正常”数字。
- 1 是一个有趣的数字
- 2 是一个有趣的数字
- 3 是一个有趣的数字
- 4 是一个有趣的数字
- …
- x 是一个正常的数字
- …
但是如果 x 是第一个“正常”数,那么它就是一个“有趣”的数,因为它有一个有趣的性质:成为第一个“正常”数。
另一方面,如果我们认为 x 是一个“有趣”的数,因为它具有作为第一个“正常”数的性质,那么它就不再是一个“正常”数,现在它是一个“有趣”数,这样就失去了作为第一个“正常”数的性质,然后不再是“有趣”的了…
真是一团糟!不“有趣”?
告诉你一个有趣的事情,这个问题就是数学家朱尔斯·理查德在 1905 年描述的“理查德数的悖论”。
这个链接(https://en.wikipedia.org/wiki/Richard%27s_paradox)讲述了更多关于理查德悖论的细节,但方式没有这里有趣。
另一个类似的悖论是“骗子悖论”。一个只会说谎的人会说“我在说谎。”但由于他只是在撒谎,所以他在这份声明中说的是实话。但如果他说的是真话,他就不是只说假话的人。
这些悖论不仅“干扰”了普通人类的思维,也干扰了历史上最伟大的数学家的思维。
奥地利数学家库尔特·哥德尔在 1931 年用他的不完全性定理摧毁了所有数学的基础,他证明了数学不可能同时是完全的和一致的。也就是数学有极限。哥德尔在数学基础中发现了一个“错误”——它不能同时摆脱这些怪异的悖论,并回答其所有命题的真或假。哥德尔用一个复杂版本的理查德悖论证明了这一点。
这是一个很长的故事,涉及到像戴维·希尔伯特和伯特兰罗素这样的思想巨人,这是另一天。
顺便说一句,我认为《建筑之光》的作者用“有趣”的方式写“正常”只是为了让建筑不再“正常”,从而迷惑我们的头脑…
其他著述:https://medium.com/@arnaldogunzi
主要博客:【https://ideiasesquecidas.com/
不等式约束优化的 ADMM-牛顿法
代码 : Github
数值优化在包括机器学习在内的许多领域都是必不可少的。在某些情况下,约束被转换为未知变量,这使得应用普通的无约束优化技术变得不那么简单,需要更复杂的方法(例如 L-BFGS-B)。
看起来,如果成本函数的梯度和 hessian 是已知的(对于像逻辑回归这样的许多问题来说都是如此),那么对于不等式约束来说,问题就相对容易了。这把钥匙叫做 ADMM 。
问题
这种 ADMM(交替方向乘子法)方法解决了下面的约束优化问题:
算法
使用罚函数和变量替换将不等式约束替换为等式约束;
在哪里,
然后,等式约束问题可以转化为它的增广拉格朗日(原始-对偶)问题:
并用 ADMM [1]求解:
Pseudocode for ADMM
原始子问题中的牛顿更新
对于原始下降 1,可以使用牛顿更新获得解决方案,这依赖于原始损失函数的梯度和 hessian 的可用性:
和下面的反演:
这种反演可以通过直接反演或迭代法(如 CG**【2】)进行。**
ADMM-牛顿法现在完成了。
示例:逻辑回归
考虑具有标签(l)、特征(f)和对权重(x)的正性约束的逻辑回归,则我们有:
使用 sigmoid 函数:
模拟数据的结果
生成 200 个样本,每个样本具有 10 维特征向量:
feature, label = make_classification(n_samples=200, n_features=10, n_redundant=0, n_informative=5, random_state=0)
和 ADMM 参数被设置为:
- \rho = 1
- max_iter = 1000
- tol = 1E-6
有/无约束条件下回归权重的比较:
约束是有效的,因为输出权重都是正的。请注意,这不再是全局最小值(由于约束),而是次优性能(AUC)。
关于方法的注释
这种方法要求损失函数 E(x)的梯度和 hessian 要么显式(作为矩阵)可用,要么隐式(使用函数)可用。
参考
- 史蒂芬·博伊德、尼尔·帕里克、朱立伦、博尔哈·佩莱托和乔纳森·埃克斯坦(2011),“通过交替方向乘数法进行分布式优化和统计学习”,《机器学习的基础和趋势:第 3 卷第 1 号,第 1–122 页。
- Jorge Nocedal,S. Wright,“数值优化”,施普林格科学,1999 年。
张量流估计类的一个高级例子
通过代码和对一些隐藏特性的深入研究。
tensor flow API 1.3 版中引入了估计器,用于抽象和简化训练、评估和预测。如果你以前没有使用过估算器,我建议从阅读这篇文章开始,熟悉一下,因为我不会涵盖使用估算器的所有基础知识。相反,我希望去神秘化和澄清一些更详细的方面,使用估值器和从现有的代码库切换到估值器。
为什么要使用估值器?
任何长期使用 Tensorflow 的人都会知道,像现在这样设置和使用 Tensorflow 需要花费大量时间。现在有许多简化开发的库,比如 slim、tflearn 等等。这通常会将定义网络所需的代码从多个文件和类减少到单个函数。他们还简化了培训和评估的管理,并在很小程度上扩展了数据准备和加载。Tensorflow 的估计类不会改变网络定义的任何内容,但它简化和抽象了管理训练、评估和预测。由于它的低级优化、有用的抽象和来自核心 Tensorflow 开发团队的支持,它从其他库中脱颖而出。
Visualization of the different API levels
这是一个很长的故事,简而言之,估算器运行和实现起来更快,更简单(一旦你习惯了),并且得到很好的支持。
文章结构
本文将使用我的一个 GitHub 项目 SqueezeNext-Tensorflow 中的例子来分析估计器的一些特性。该项目中实现的网络来自于 2018 年发布的一篇名为“ SqueezeNext ”的论文。由于采用了一种新颖的可分离卷积方法,这种网络非常轻便和快速。研究人员发布了一个 caffe 版本,但为了便于使用可用的 Tensorflow 库进行实验,我在 Tensorflow 中重新创建了论文中的算法。
文章使用以下结构设置:
- 设置评估器
- 使用估算器和数据集加载数据
- 定义预测、训练和评估模式
- 会话挂钩和脚手架
- 预言;预测;预告
设置评估器
为了为某个培训和评估花名册设置评估者,最好先了解评估者设置如何工作。要构造 Estimator 类的实例,可以使用以下调用:
classifier = tf.estimator.Estimator(model_dir=model_dir,
model_fn=model_fn,
params=params)
在这个调用中,“model_dir”是估计器应该存储和加载检查点和事件文件的文件夹的路径。“model_fn”参数是一个按以下顺序使用特征、标签、模式和参数的函数:
def model_fn(features, labels, mode, params):
当估计器执行用于训练、评估或预测的模型函数时,它将总是提供那些参数。要素参数包含一个张量字典,其中包含要输入到网络的要素,标注参数包含一个张量字典,其中包含要用于训练的标注。这两个参数由输入 fn 产生,这将在后面解释。第三个参数 mode 描述“model_fn”是否被调用用于训练、评估或预测。最后一个参数 params 是一个简单的字典,它可以包含 python 变量,并且可以在网络定义过程中使用(考虑学习速率计划的总步骤等)。).
培训和评估示例
既然 Estimator 对象已经初始化,就可以用它来开始训练和评估网络。下面是 train.py 的摘录,它实现了上面的指令来创建一个估计器对象,然后开始训练和评估。
From: https://github.com/Timen/squeezenext-tensorflow/blob/master/train.py
这段代码首先为 params 设置配置字典,并使用它和“model_fn”一起构造一个估计器对象。然后它创建一个“ TrainSpec ”,带有“输入 _fn”和模型应该训练的“最大 _ 步数”。做类似的事情来创建“ EvalSpec ”,其中“步骤”是每次评估的步骤数,“throttle_secs”定义每次评估之间的间隔(以秒为单位)。“TF . estimator . train _ and _ evaluate”用于使用 Estimator 对象“ TrainSpec ”和“ EvalSpec ”启动训练和评估花名册。最后,在训练结束后,再次显式调用评估。
使用估算器和数据集加载数据
在我看来,关于估算器最好的事情是,你可以很容易地将它们与数据集类结合起来。在评估器和数据集类结合之前,很难从 GPU 异步地在 CPU 上预取和处理示例。理论上,CPU 上的预取和处理将确保在 GPU 完成处理前一批样本的任何时候,内存中都会有一批准备好的样本,实际上这说起来容易做起来难。仅将获取和处理步骤分配给 CPU 的问题是,除非它与 GPU 上的模型处理并行完成,否则 GPU 仍然必须等待 CPU 从存储磁盘获取数据并进行处理,然后才能开始处理下一批数据。很长一段时间以来,queuerunners、使用 python 线程的异步预取和其他解决方案都被提出,以我的经验来看,没有一个能够完美而高效地工作。
但是使用 Estimator 类并将其与 Dataset 类结合起来非常简单、干净,并且可以与 GPU 并行工作。它允许 CPU 获取、预处理和终止一批示例,以便总是有新的一批为 GPU 准备好。使用这种方法,我看到在训练期间 GPU 的利用率接近 100%,对于小模型(<10MB) increase 4 fold.
Dataset Class
The Tensorflow Dataset class is designed as an E.T.L )每秒的全局步数。process,代表提取、转换和加载。这些步骤将很快被定义,但是本指南将只解释如何将 tfrecords 与 Dataset 类结合使用。对于其他格式(csv、numpy 等。)这个页面有很好的报道,但是我建议使用 tfrecords,因为它们提供更好的性能,并且更容易与 Tensorflow 开发管道集成。
From: http://blog.appliedinformaticsinc.com/etl-extract-transform-and-load-process-concept/
整个 E.T.L .过程可以使用 Dataset 类实现,只需 7 行代码,如下所示。一开始看起来可能很复杂,但请继续阅读,了解每一行功能的详细解释。
From: https://github.com/Timen/squeezenext-tensorflow/blob/master/dataloader.py
摘录
数据集输入管道的第一步是将 tfrecords 中的数据加载到内存中。这从使用 glob 模式(例如“)生成可用的 tfrecords 列表开始。/数据集/训练-*。tfrecords”和 Dataset 类的 list_files 函数。parallel_interleave 函数应用于文件列表,确保并行提取数据,如这里的所述。最后,使用合并的混洗和重复函数从 tfrecords 中预取一定数量的样本并混洗它们。一旦读取了每个 tfrecord 的最后一个示例,repeat 通过从头开始重复来确保总是有可用的示例。
files = tf.data.Dataset.list_files(glob_pattern, shuffle=True)dataset = files.apply(tf.contrib.data.parallel_interleave(
lambda filename:
tf.data.TFRecordDataset(filename),
cycle_length=threads*2)
)dataset = dataset.apply(tf.contrib.data.shuffle_and_repeat
(32*self.batch_size))
变身
现在,数据在存储器中可用,下一步是将其转换,最好转换成不需要任何进一步处理的东西,以便馈送到神经网络输入。要做到这一点,需要调用数据集的 map 函数,如下所示,其中“map_func”是应用于 CPU 上每个单独示例的函数,“num_parallel_calls”是要使用的“map_func”的并行调用次数。
threads = multiprocessing.cpu_count()dataset = dataset.map(map_func=lambda example:
_parse_function(example, self.image_size,
self.num_classes,training=training),
num_parallel_calls=threads)
在这种情况下,“map_func”是如下所示的解析函数,该函数处理来自 tfrecords(使用此 repo 创建)的示例,并输出包含分别代表特征和标签的张量的字典元组。注意使用 lambda 函数将 python 变量从示例中单独传递给 parse 函数,因为示例是来自 tfrecord 的未解析数据,由 Dataset 类提供。
From: https://github.com/Timen/squeezenext-tensorflow/blob/master/dataloader.py
请记住,这个解析函数一次只处理一个示例,如 tf.parse_single_example 调用所示,但是会并行处理多次。为了防止遇到任何 CPU 瓶颈,保持解析函数快速运行是很重要的,关于如何做到这一点的一些提示可以在这里找到。然后,所有单独处理的样本被分批并准备处理。
dataset = dataset.batch(batch_size=self.batch_size)
加载
ETL 过程的最后一步是将批量示例加载到加速器(GPU)上,准备进行处理。在数据集类中,这是通过预取来实现的,预取是通过调用数据集的预取函数来完成的。
dataset = dataset.prefetch(buffer_size=self.batch_size)
预取将生产者(CPU 上的数据集对象)与消费者(GPU)分开,这允许它们并行运行以提高吞吐量。
输入功能
一旦完整定义并实现了整个 E.T.L .过程,就可以通过初始化迭代器并使用下面的代码行获取下一个示例来创建“input_fn ”:
input_fn = dataset.make_one_shot_iterator().get_next()
该输入函数被估计器用作模型函数的输入。
定义预测、训练和评估模式
快速提醒一下,评估者在训练、评估和预测期间调用的模型函数应该接受前面解释的以下参数:
def model_fn(features, labels, mode, params):
在这种情况下,要素和标注由数据集类提供,参数主要用于网络初始化期间使用的超参数,但模式(类型为“tf.estimator.ModeKeys”)决定了模型将要执行的操作。每种模式都用于为特定目的建立模型,可用的模式有预测、训练和评估。
From: https://k-d-w.org/blog/103/denoising-autoencoder-as-tensorflow-estimator
可以通过调用估算器对象的相应函数(如上所示)来使用不同的模式,即:“预测”、“训练”和“评估”。每个模式的代码路径必须返回一个带有该模式所需字段的“估计规格”,例如,当模式为预测模式时,它必须返回一个包括预测字段的“估计规格”:
return tf.estimator.EstimatorSpec(mode, predictions=predictions)
预测
最基本的模式是预测模式"TF . estimator . mode keys . predict",顾名思义就是使用 Estimator 对象对数据进行预测。在这种模式下,“EstimatorSpec”需要一个将要执行的张量字典,其结果将作为 numpy 值提供给 python。
From: https://github.com/Timen/squeezenext-tensorflow/blob/master/squeezenext_model.py
在这段摘录中,您可以看到预测字典设置用于生成经典图像分类结果。if 语句确保该代码路径仅在执行 Estimator 对象的 predict 函数时执行。张量字典作为预测参数与模式一起传递给“EstimatorSpec”。首先定义预测代码路径是明智的,因为它是最简单的,并且因为大部分代码也用于训练和评估,所以它可以在早期显示问题。
列车
为了在"TF . estimator . mode keys . train"模式中训练模型,有必要创建一个所谓的" train_op ",该 op 是一个张量,当执行时执行反向传播以更新模型。简单地说,它是优化器的最小化功能,比如 AdamOptimizer 。“train_op”和标量损失张量是创建用于训练的“EstimatorSpec”所需的最小参数。下面你可以看到一个这样做的例子。
From: https://github.com/Timen/squeezenext-tensorflow/blob/master/squeezenext_model.py
请注意非必需的参数“training_hooks”和“scaffold ”,这些将在后面进一步解释,但简而言之,它们用于向模型和培训会话的设置和拆除添加功能。
评估
模型函数中需要代码路径的最后一个模式是“TF . estimator . mode keys . eval”。为了执行评估,最重要的是度量字典,它应该被构造为元组字典,其中元组的第一个元素是包含实际度量值的张量,第二个元素是更新度量值的张量。更新操作对于确保整个验证集的可靠度量计算是必要的。因为通常不可能在一个批次中评估整个验证集,所以必须使用多个批次。为了防止由于每批差异而在度量值中产生噪声,使用更新操作来保持所有批的运行平均值(或收集所有结果)。此设置可确保在整个验证集中计算指标值,而不是在单个批次中计算。
From: https://github.com/Timen/squeezenext-tensorflow/blob/master/squeezenext_model.py
在上面的示例中,只有“loss”和“eval_metric_ops”是必需的参数,第三个参数“evaluation_hooks”用于执行“tf.summary”操作,因为在运行评估者评估函数时不会自动执行这些操作。在本例中,“evaluation_hooks”用于存储来自验证集的图像,以便使用 Tensorboard 显示。为了实现这一点,使用“tf.summary.image”操作来初始化具有与“model_dir”相同的输出目录的“SummarySaverHook ”,并将其传递(封装在 iterable 中)给“EstimatorSpec”。
切换到估计类
既然我解释了使用 Estimator 类的优点以及如何使用它,我希望您对开始在 Tensorflow 项目中使用 Estimator 感到兴奋。然而,从现有的代码库切换到估计器并不一定简单。由于 Estimator 类在训练期间抽象掉了大部分的初始化和执行,任何定制的初始化和执行循环都不再是使用“ tf 的实现。Session 和“ sess.run() ”。这可能是拥有大量代码的人不过渡到估算者类的原因,因为没有简单直接的过渡途径。虽然本文不是一个过渡指南,但我将阐明一些初始化和执行的新过程。这将有望填补官方教程留下的一些空白。
脚手架和会话脱钩
影响初始化和执行循环的主要工具是“Scaffold”对象和“SessionRunHook”对象。“脚手架”用于模型的自定义首次初始化,并且只能用于在训练模式下构建“估计规格”。另一方面,“SessionRunHook”可用于为每种执行模式构建“EstimatorSpec ”,并在每次调用 train、evaluate 或 predict 时使用。“Scaffold”和“SessionRunHook”都提供了 Estimator 类在使用过程中调用的某些函数。下面你可以看到一条时间线,显示在初始化和训练过程中调用哪个函数以及何时调用。这也说明了引擎盖下的估计器还是用“ tf。会话和会话。
Function execution timeline executed by the Estimator of the Scaffold and SessionRunHook classes.
脚手架
在构造用于训练的“EstimatorSpec”时,可以将一个脚手架作为脚手架参数传递。在 scaffold 中,您可以指定在初始化的不同阶段要调用的许多不同操作。然而,现在我们将把重点放在“ init_fn ”参数上,因为其他参数更多地是针对分布式设置的,本系列将不会涉及。在图形完成之后,但在第一次调用“ sess.run ”之前,调用“ init_fn ”,它只使用一次,因此非常适合自定义变量初始化。一个很好的例子是,如果您想要使用预训练网络进行微调,并且想要选择性地恢复哪些变量。
From: https://github.com/Timen/squeezenext-tensorflow/blob/master/tools/fine_tune.py
上面你可以看到一个实现的例子,该函数检查微调检查点的存在,并使用 slim 创建一个初始化函数,该函数过滤" scope_name "变量。然后,该函数被封装在估计器期望的函数格式中,该格式消耗“支架”对象本身和一个“ tf”。会话对象。在“ init_fn ”内,会话用于运行“ initializer_fn ”。
# setup fine tune scaffold
scaffold = tf.train.Scaffold(init_op=None,
init_fn=tools.fine_tune.init_weights(params["fine_tune_ckpt"]))# create estimator training spec
return tf.estimator.EstimatorSpec(tf.estimator.ModeKeys.TRAIN,
loss=loss,
train_op=train_op,scaffold=scaffold)
这个" init_fn “然后可以作为一个参数传递,以构建一个” Scaffold "对象,如上所示。这个“脚手架”对象用于构建列车模式的“估计规格”。虽然这是一个相对简单的示例(也可以通过“WarmStartSettings”实现),但它可以轻松扩展为更高级的一次性初始化功能。
会话脱钩
如前面的时间线所示,“session unhook”类可用于改变训练、评估或预测循环的某些部分。这是通过使用一个叫做挂钩的概念来完成的,我不会详细说明挂钩到底是什么,因为在这种情况下,它是不言自明的。但是使用“SessionRunHook”与使用脚手架略有不同。它不是用一个或多个函数作为参数来初始化“SessionRunHook”对象,而是被用作一个超类。然后,可以重写方法“ begin ”、“ after_create_session ”、“ before_run ”、“ after_run ”和“ end ”,以扩展功能。以下摘录显示了如何覆盖“ begin ”功能。
From: https://github.com/Timen/squeezenext-tensorflow/blob/master/tools/stats.py
请记住" begin "函数不获取任何参数,但每个挂钩略有不同,请参考这里的来检查哪些参数被传递给了哪个函数。现在,用户可以通过调用扩展的 begin 方法创建一个“SessionRunHook ”:
stats_hook = tools.stats.ModelStats("squeezenext",
params["model_dir"],
self.batch_size)
然后,在构建用于训练、评估和预测的“EstimatorSpec”时,可以通过将该对象传递给相应的参数“training_hooks”、“evaluation_hooks”和“prediction_hooks”来使用该对象。请注意,每个参数都以钩子结尾,它们需要一个包含一个或多个钩子的 iterable,所以请始终将钩子封装在 iterable 中。
预言;预测;预告
培训和评估已被广泛涵盖,但预测尚未得到充分解释。这主要是因为预测是 3 种(训练、评估、预测)估计模式中最容易的,但是仍然会遇到一些陷阱。我在前面解释了如何为预测设置一个“EstimatorSpec ”,但没有解释如何使用它。下面是一个小例子,展示了如何使用估计量进行预测。
From: https://github.com/Timen/squeezenext-tensorflow/blob/master/predict.py
对于预测来说,生成/使用 tfrecords 没有意义,因此使用了“ numpy_input_fn ”。向" x "参数传递一个包含特征的 numpy 数组字典,在本例中是一个图像。
注意:根据您是否在训练期间预处理" input_fn “中的图像,您可能需要在这里执行相同的预处理,或者在传递到” numpy_input_fn "之前在 numpy 中执行,或者在 Tensorflow 中执行。
使用 numpy 输入函数设置,一个简单的预测调用就足以创建一个预测。上例中的"预测"变量实际上并不包含结果,相反,它是 generator 类的一个对象,要得到实际的结果,你需要迭代它。这个迭代将启动 Tensorflow 执行并产生实际结果。
结束语
我希望我能够揭开张量流估值器的一些未被记录的特性的神秘面纱,并填补官方教程留下的空白。这篇文章中的所有代码都来自这个 github repo ,所以请前往那里查看与 Datasets 类结合使用的估计器的示例。如果你还有任何问题,请留下来,我会尽力回答你。
奥运标志评分分析
今天早上,我在 Quartz 上读到了一篇关于奥运标志的有趣文章。米尔顿·格拉泽(Milton Glaser)是许多其他项目中“我❤纽约”标志背后的获奖平面设计师,他对 1924 年以来的每届夏季和冬季奥运会标志进行了评级。我觉得看看他的收视率有没有什么有趣的规律会很有趣。
首先,我们将使用“rvest”包提取包含所有分数的文本。然后我们将过滤掉无关的文本,这样我们只剩下分数。
# clear the way
rm(list=ls())# load libraries
library(dplyr)
library(ggplot2)
library(plotly)
library(rvest)# extract score for each Olympics
olympics_logo_scores <-
read_html("[http://qz.com/755251/on-a-scale-from-1-100-milton-glaser-rates-every-single-olympic-logo-design-in-history/](http://qz.com/755251/on-a-scale-from-1-100-milton-glaser-rates-every-single-olympic-logo-design-in-history/)") %>%
html_nodes('p') %>%
html_text()# extract scores
olympics_logo_scores <- olympics_logo_scores[which(substr(olympics_logo_scores, 1, 5) == 'Score')]
然后,我们将进行第二次呼叫,以获取每届奥运会的名称。
# Get olympics names
olympics_logo_names <-
read_html("[http://qz.com/755251/on-a-scale-from-1-100-milton-glaser-rates-every-single-olympic-logo-design-in-history/](http://qz.com/755251/on-a-scale-from-1-100-milton-glaser-rates-every-single-olympic-logo-design-in-history/)") %>%
html_nodes('h2') %>%
html_text()
我们将把名字和分数数据合并到一个数据帧中,此外还有一列表示奥运会是在夏季还是冬季举行的。
# create combined df
olympic_logo_score_df <-
data.frame('olympics' = olympics_logo_names,
'logo_score' = olympics_logo_scores,
'season' = ifelse(grepl('Summer', olympics_logo_names), 'summer', 'winter'))# convert from factor to character
olympic_logo_score_df$olympics <- as.character(olympic_logo_score_df$olympics)
然后,我们将分两步从每一行中提取分数:首先,我们将拆分“out”上的字符串,去掉每个得分短语的第二部分,其中包含“满分 100 分”
# extract score (part 1 of 2)
olympic_logo_score_df$logo_score <- sapply(strsplit(as.character(olympic_logo_score_df$logo_score), 'out'), `[[`, 1)
然后我们将删除所有非数字字符,只留下数字分数。你会注意到有一个 NA,它与 2020 年东京夏季奥运会相关联。
# extract score (part 2 of 2)
olympic_logo_score_df$logo_score <- as.numeric(gsub("[^0-9]", "", olympic_logo_score_df$logo_score))
我们将使用类似的方法从完整的奥林匹克名称中提取数字年份。
# drop alpha characters
olympic_logo_score_df$year <- gsub("[^0–9]", "", olympic_logo_score_df$olympics)
我马上注意到的第一件事是夏季和冬季奥运会在同一年举行,直到 1992 年以后。92 年后,冬季奥运会在每届夏季奥运会后两年举行(或者两年前,取决于你对哪个更感兴趣)。整洁!
我在这个分析中的主要问题是时间和标志等级之间的关系。Glaser 比老游戏更喜欢新 logos 吗?让我们找出答案。使用 plotly,我们可以绘制按季节分组的徽标等级随时间变化的图表。
# graph scores over time by season
plot_ly(olympic_logo_score_df,
x = year,
y = logo_score,
type = "line",
group = season,
text = olympics) %>%
add_trace(y = fitted(loess(logo_score ~ year)),
x = year,
name = 'Overall trend',
line = list(dash = 5)) %>%
layout(title = 'Olympics Logo Scores Over Time',
yaxis = list(range = list(0,100),
title = 'Score'),
xaxis = list(title = 'Year'))
Fully interactive plot available here
我们可以看到格拉泽确实更喜欢现代的标志,而不是早期夏季和冬季奥运会的标志。
与夏季奥运会相比,冬季奥运会的标志似乎普遍低于趋势水平。让我们调查一下他的偏好是否会因奥运会的类型而异。
# boxplot by season
plot_ly(olympic_logo_score_df,
y = logo_score,
type = "box",
group = season,
boxpoints = "all",
pointpos = 0) %>%
layout(title = 'Olympics Logo Scores by Season',
yaxis = list(range = list(0,100),
title = 'Score'),
xaxis = list(title = 'Season'))
Fully interactive plot available here
我们可以看到夏季奥运会标志的分数比冬季奥运会标志的分数有更大的变化。然而,夏季奥运会标志的中位数也更高,表明 Glaser 通常更喜欢夏季奥运会标志。
一个使用人工智能将图形设计转换为功能代码的应用程序。
Tony Beltramelli (https://uizard.io)
作为 Idyee 的前端开发人员,我的工作包括根据客户的要求设计和开发定制用户界面(应用程序的外观和感觉)。这是为 web、IOS 和 Android 开发而做的。在大多数情况下,在客户满意之前,我们需要经历如此多的迭代。之后,我们需要将图形转换成使用 HTML、CSS、JavaScript 编写的代码。
这一繁琐的过程显然意味着我们将有更少的时间来开发应用程序的核心功能。可以说,需要一个解决方案来确保客户端应用程序的设计和开发尽可能高效。输入 P ix2Code 。
Pix2Code 是一款由总部位于哥本哈根的人工智能初创公司开发的应用,名为 UIzard Technologies 。该应用程序使用一个经过训练的神经网络将截图转化为代码行。它需要一个用于用户界面设计的图像作为输入,然后将其转换成相应的代码。
该应用程序令人印象深刻的一个特点是,它生成的代码与 Android、IOS 和 Web 技术兼容,准确率为 77 %。你可以阅读Arxiv上发表的文献。
UIzard 的创始人 Tony Beltramelli 认为 Pix2Code 有潜力“终结对手工编程图形用户界面的需求”。我说机器人万岁。😃
使用 Python 进行时间序列分析和预测的端到端项目
Photo credit: Pexels
时间序列分析包括分析时间序列数据的方法,以提取有意义的统计数据和数据的其他特征。时间序列预测是使用模型根据以前观察到的值来预测未来值。
时间序列广泛用于非平稳数据,如经济、天气、股票价格和本文中的零售额。我们将展示预测零售时间序列的不同方法。我们开始吧!
数据
import warnings
import itertools
import numpy as np
import matplotlib.pyplot as plt
warnings.filterwarnings("ignore")
plt.style.use('fivethirtyeight')
import pandas as pd
import statsmodels.api as sm
import matplotlibmatplotlib.rcParams['axes.labelsize'] = 14
matplotlib.rcParams['xtick.labelsize'] = 12
matplotlib.rcParams['ytick.labelsize'] = 12
matplotlib.rcParams['text.color'] = 'k'
在超市销售数据中有几个类别,我们从家具销售的时间序列分析和预测开始。
df = pd.read_excel("Superstore.xls")
furniture = df.loc[df['Category'] == 'Furniture']
我们有良好的 4 年家具销售数据。
furniture['Order Date'].min(), furniture['Order Date'].max()
时间戳(’ 2014–01–06 00:00:00 ‘),时间戳(’ 2017–12–30 00:00:00 ')
数据预处理
这一步包括删除我们不需要的列,检查缺少的值,按日期合计销售额等等。
cols = ['Row ID', 'Order ID', 'Ship Date', 'Ship Mode', 'Customer ID', 'Customer Name', 'Segment', 'Country', 'City', 'State', 'Postal Code', 'Region', 'Product ID', 'Category', 'Sub-Category', 'Product Name', 'Quantity', 'Discount', 'Profit']
furniture.drop(cols, axis=1, inplace=True)
furniture = furniture.sort_values('Order Date')furniture.isnull().sum()
Figure 1
furniture = furniture.groupby('Order Date')['Sales'].sum().reset_index()
使用时间序列数据进行索引
furniture = furniture.set_index('Order Date')
furniture.index
Figure 2
我们当前的日期时间数据可能很难处理,因此,我们将使用该月的平均日销售额,并且我们使用每个月的月初作为时间戳。
y = furniture['Sales'].resample('MS').mean()
快速一瞥 2017 年家具销售数据。
y['2017':]
Figure 3
可视化家具销售时间序列数据
y.plot(figsize=(15, 6))
plt.show()
Figure 4
当我们绘制数据时,出现了一些明显的模式。该时间序列具有季节性模式,例如销售额总是在年初低,在年底高。在任何一年中,总会有一个上升的趋势,在年中会有几个低的月份。
我们还可以使用一种称为时间序列分解的方法来可视化我们的数据,这种方法允许我们将时间序列分解为三个不同的部分:趋势、季节性和噪声。
from pylab import rcParams
rcParams['figure.figsize'] = 18, 8decomposition = sm.tsa.seasonal_decompose(y, model='additive')
fig = decomposition.plot()
plt.show()
Figure 5
上图清楚地显示了家具销售的不稳定性,以及其明显的季节性。
ARIMA 时间序列预测
我们将应用一种最常用的时间序列预测方法,称为 ARIMA,它代表自回归综合移动平均。
ARIMA 模型用符号ARIMA(p, d, q)
表示。这三个参数说明了数据中的季节性、趋势和噪声:
p = d = q = range(0, 2)
pdq = list(itertools.product(p, d, q))
seasonal_pdq = [(x[0], x[1], x[2], 12) for x in list(itertools.product(p, d, q))]print('Examples of parameter combinations for Seasonal ARIMA...')
print('SARIMAX: {} x {}'.format(pdq[1], seasonal_pdq[1]))
print('SARIMAX: {} x {}'.format(pdq[1], seasonal_pdq[2]))
print('SARIMAX: {} x {}'.format(pdq[2], seasonal_pdq[3]))
print('SARIMAX: {} x {}'.format(pdq[2], seasonal_pdq[4]))
Figure 6
这一步是为我们的家具销售 ARIMA 时间序列模型选择参数。我们在这里的目标是使用“网格搜索”来找到为我们的模型产生最佳性能的最佳参数集。
for param in pdq:
for param_seasonal in seasonal_pdq:
try:
mod = sm.tsa.statespace.SARIMAX(y,
order=param,
seasonal_order=param_seasonal,
enforce_stationarity=False,
enforce_invertibility=False)results = mod.fit()print('ARIMA{}x{}12 - AIC:{}'.format(param, param_seasonal, results.aic))
except:
continue
Figure 7
上面的输出表明SARIMAX(1, 1, 1)x(1, 1, 0, 12)
产生最低的AIC
值 297.78。因此,我们应该认为这是最佳选择。
拟合 ARIMA 模型
mod = sm.tsa.statespace.SARIMAX(y,
order=(1, 1, 1),
seasonal_order=(1, 1, 0, 12),
enforce_stationarity=False,
enforce_invertibility=False)results = mod.fit()print(results.summary().tables[1])
Figure 8
我们应该始终运行模型诊断来调查任何不寻常的行为。
results.plot_diagnostics(figsize=(16, 8))
plt.show()
Figure 9
它并不完美,但是,我们的模型诊断表明,模型残差接近正态分布。
验证预测
为了帮助我们了解预测的准确性,我们将时间序列的预测销售额与实际销售额进行比较,并将预测从 2017 年 1 月 1 日开始设置到数据结束。
pred = results.get_prediction(start=pd.to_datetime('2017-01-01'), dynamic=False)
pred_ci = pred.conf_int()ax = y['2014':].plot(label='observed')
pred.predicted_mean.plot(ax=ax, label='One-step ahead Forecast', alpha=.7, figsize=(14, 7))ax.fill_between(pred_ci.index,
pred_ci.iloc[:, 0],
pred_ci.iloc[:, 1], color='k', alpha=.2)ax.set_xlabel('Date')
ax.set_ylabel('Furniture Sales')
plt.legend()plt.show()
Figure 10
线形图显示了观测值与滚动预测值的对比。总的来说,我们的预测与真实值非常吻合,显示了从年初开始的上升趋势,并在年底抓住了季节性。
y_forecasted = pred.predicted_mean
y_truth = y['2017-01-01':]mse = ((y_forecasted - y_truth) ** 2).mean()
print('The Mean Squared Error of our forecasts is {}'.format(round(mse, 2)))
我们预测的均方差是 22993.58
print('The Root Mean Squared Error of our forecasts is {}'.format(round(np.sqrt(mse), 2)))
我们预测的均方根误差是 151.64
在统计学中,估计量的均方误差(MSE) 衡量误差的平方平均值,即估计值和估计值之间的平均平方差。MSE 是对估计量质量的一种度量,它总是非负的,MSE 越小,我们就越接近找到最佳拟合线。
均方根误差(RMSE) 告诉我们,我们的模型能够在实际销售额的 151.64 范围内预测测试集中的平均每日家具销售额。我们的家具日销售量从 400 件左右到 1200 多件不等。在我看来,到目前为止这是一个相当不错的模型。
生成和可视化预测
pred_uc = results.get_forecast(steps=100)
pred_ci = pred_uc.conf_int()ax = y.plot(label='observed', figsize=(14, 7))
pred_uc.predicted_mean.plot(ax=ax, label='Forecast')
ax.fill_between(pred_ci.index,
pred_ci.iloc[:, 0],
pred_ci.iloc[:, 1], color='k', alpha=.25)
ax.set_xlabel('Date')
ax.set_ylabel('Furniture Sales')plt.legend()
plt.show()
Figure 11
我们的模型清楚地捕捉到了家具销售的季节性。随着我们对未来的进一步预测,我们对自己的价值观变得不那么自信是很自然的。这反映在我们的模型生成的置信区间上,随着我们向未来进一步发展,置信区间变得更大。
上面对家具的时间序列分析让我对其他类别感到好奇,随着时间的推移,它们是如何相互比较的。因此,我们将比较家具和办公用品供应商的时间序列。
家具与办公用品的时间序列
根据我们的数据,这些年来,办公用品的销售额远远高于家具。
furniture = df.loc[df['Category'] == 'Furniture']
office = df.loc[df['Category'] == 'Office Supplies']
furniture.shape, office.shape
((2121,21),(6026,21))
数据探索
我们将比较两个类别在同一时期的销售额。这意味着将两个数据帧合并成一个,并将这两个类别的时间序列绘制成一个图。
cols = ['Row ID', 'Order ID', 'Ship Date', 'Ship Mode', 'Customer ID', 'Customer Name', 'Segment', 'Country', 'City', 'State', 'Postal Code', 'Region', 'Product ID', 'Category', 'Sub-Category', 'Product Name', 'Quantity', 'Discount', 'Profit']
furniture.drop(cols, axis=1, inplace=True)
office.drop(cols, axis=1, inplace=True)furniture = furniture.sort_values('Order Date')
office = office.sort_values('Order Date')furniture = furniture.groupby('Order Date')['Sales'].sum().reset_index()
office = office.groupby('Order Date')['Sales'].sum().reset_index()furniture = furniture.set_index('Order Date')
office = office.set_index('Order Date')y_furniture = furniture['Sales'].resample('MS').mean()
y_office = office['Sales'].resample('MS').mean()furniture = pd.DataFrame({'Order Date':y_furniture.index, 'Sales':y_furniture.values})
office = pd.DataFrame({'Order Date': y_office.index, 'Sales': y_office.values})store = furniture.merge(office, how='inner', on='Order Date')
store.rename(columns={'Sales_x': 'furniture_sales', 'Sales_y': 'office_sales'}, inplace=True)
store.head()
Figure 12
plt.figure(figsize=(20, 8))
plt.plot(store['Order Date'], store['furniture_sales'], 'b-', label = 'furniture')
plt.plot(store['Order Date'], store['office_sales'], 'r-', label = 'office supplies')
plt.xlabel('Date'); plt.ylabel('Sales'); plt.title('Sales of Furniture and Office Supplies')
plt.legend();
Figure 13
我们观察到家具和办公用品的销售也有类似的季节性模式。年初是这两个类别的淡季。对于办公用品来说,夏天似乎也很安静。此外,在大多数月份里,家具的平均日销售额都高于办公用品。这是可以理解的,因为家具的价值应该远远高于办公用品。偶尔,办公用品的日平均销售额会超过家具。让我们来看看办公用品的销售额第一次超过家具的销售额是什么时候。
first_date = store.ix[np.min(list(np.where(store['office_sales'] > store['furniture_sales'])[0])), 'Order Date']print("Office supplies first time produced higher sales than furniture is {}.".format(first_date.date()))
办公用品首次产生高于家具的销量是 2014–07–01。
那是 2014 年 7 月!
用 Prophet 进行时间序列建模
脸书于 2017 年发布的预测工具 Prophet 旨在分析时间序列,这些时间序列在不同的时间尺度上显示模式,如每年、每周和每天。它还具有高级功能,可以对节假日对时间序列的影响进行建模,并实现自定义的变点。因此,我们使用 Prophet 来启动并运行一个模型。
from fbprophet import Prophetfurniture = furniture.rename(columns={'Order Date': 'ds', 'Sales': 'y'})
furniture_model = Prophet(interval_width=0.95)
furniture_model.fit(furniture)office = office.rename(columns={'Order Date': 'ds', 'Sales': 'y'})
office_model = Prophet(interval_width=0.95)
office_model.fit(office)furniture_forecast = furniture_model.make_future_dataframe(periods=36, freq='MS')
furniture_forecast = furniture_model.predict(furniture_forecast)office_forecast = office_model.make_future_dataframe(periods=36, freq='MS')
office_forecast = office_model.predict(office_forecast)plt.figure(figsize=(18, 6))
furniture_model.plot(furniture_forecast, xlabel = 'Date', ylabel = 'Sales')
plt.title('Furniture Sales');
Figure 14
plt.figure(figsize=(18, 6))
office_model.plot(office_forecast, xlabel = 'Date', ylabel = 'Sales')
plt.title('Office Supplies Sales');
Figure 15
比较预测
我们已经有了这两个类别未来三年的预测。我们现在将他们放在一起比较他们对未来的预测。
furniture_names = ['furniture_%s' % column for column in furniture_forecast.columns]
office_names = ['office_%s' % column for column in office_forecast.columns]merge_furniture_forecast = furniture_forecast.copy()
merge_office_forecast = office_forecast.copy()merge_furniture_forecast.columns = furniture_names
merge_office_forecast.columns = office_namesforecast = pd.merge(merge_furniture_forecast, merge_office_forecast, how = 'inner', left_on = 'furniture_ds', right_on = 'office_ds')forecast = forecast.rename(columns={'furniture_ds': 'Date'}).drop('office_ds', axis=1)
forecast.head()
Figure 16
趋势和预测可视化
plt.figure(figsize=(10, 7))
plt.plot(forecast['Date'], forecast['furniture_trend'], 'b-')
plt.plot(forecast['Date'], forecast['office_trend'], 'r-')
plt.legend(); plt.xlabel('Date'); plt.ylabel('Sales')
plt.title('Furniture vs. Office Supplies Sales Trend');
Figure 17
plt.figure(figsize=(10, 7))
plt.plot(forecast['Date'], forecast['furniture_yhat'], 'b-')
plt.plot(forecast['Date'], forecast['office_yhat'], 'r-')
plt.legend(); plt.xlabel('Date'); plt.ylabel('Sales')
plt.title('Furniture vs. Office Supplies Estimate');
Figure 18
趋势和模式
现在,我们可以使用 Prophet 模型来检查数据中这两个类别的不同趋势。
furniture_model.plot_components(furniture_forecast);
Figure 19
office_model.plot_components(office_forecast);
Figure 20
很高兴看到家具和办公用品的销售一直呈线性增长,并将继续增长,尽管办公用品的增长似乎略显强劲。
家具最差的月份是四月,办公用品最差的月份是二月。家具最好的月份是 12 月,办公用品最好的月份是 10 月。
今后可以探索的时间序列分析方法有很多,如不确定界预测、变点和异常检测、外部数据源预测时间序列等。我们才刚刚开始。
源代码可以在 Github 上找到。我期待听到反馈或问题。
参考资料:
道德准则不可能是关于道德的
这就是为什么我不能支持 Data for Democracy 的数据科学道德准则。
Stotting (a costly signal): the jumping up and down proves to a predator that the animal isn’t an easy target
【编辑:我试图在这里对科技伦理这个主题做一个更全面的总结:https://hacker noon . com/can-we-be-honest-about-ethics-ECF 5840 b6e 07
上周,我写了一篇文章,对 Data for Democracy 创建数据科学道德准则的意图表示怀疑。我关注的是这个项目的实际可行性。在大量谈论、阅读和观察 D4D 道德准则的演变之后,我仍然相信这些被提议的原则在很大程度上是不可行的。我现在还认为,工作组产生的成果完全建立在错误的基础上。这不是向前迭代一个解决方案。如果你构建的是错误的东西,再多的修改也不会成功。
我们需要明确道德准则的含义。如果我们可以现实地期望社区中的每个人都采纳道德规范,因为他们直觉地认为这是好的和正确的事情,那么道德规范是不必要的——它只不过是美德信号。如果我们不能现实地期望完全的有机采用,那么代码就是一种机制,来胁迫那些不同意它的人,来谴责那些不遵守它的人。这两条途径——大规模的自愿采纳或强制——是道德准则真正有意义的唯一两种方式。
一个职业中的任何一部分人有什么权利宣布这个职业中的每个人什么是对的或者什么是错的?完全没有权利。任何人都没有权利对他人发号施令,但有时一些人可以获得足够的权力来这样做。在一系列非常特殊的情况下,整个行业都可以从中受益。
越想越对希波克拉底誓言印象深刻。在我看来,医疗职业是基于两个前提:一个不健康的人,平均来说,在与医生的互动过程中,应该变得更健康;一个健康的人,平均来说,在与医生的互动过程中,不会变得不健康。这就是医生的谋生之道:医生个人从这两个前提中获益,这对大多数人来说是真实的。
如果这两个前提都不成立,任何允许医生个人从当医生中获益的做法都会损害每个人信任医生的能力。如果一个医生不能治愈病人而仍然保持繁荣,那么“医生”的头衔,或者医学学位,或者医学职业的其他标志就毫无意义。
希波克拉底誓言的每一个组成部分都要求医生故意限制自己的繁荣,要么以自己的代价让他人受益,要么拒绝从通常会成为公平游戏的情况中受益。这是关键。希波克拉底誓言是一个有效的道德准则,因为它不是对与错的陈述。这是一系列昂贵的信号。
昂贵的信号是故意将自己置于不利地位的做法,以显示你能够承受这种牺牲。一些瞪羚消耗能量上蹿下跳向掠食者展示它们有如此多的能量可以浪费,证明它们太强壮太快了,掠食者不会打扰它们。宗教团体的人们牺牲资源来证明他们从团体中得到足够的东西来弥补损失。昂贵的信号是一种将皮放进游戏的方式,纳西姆·塔勒布(Nassim Taleb)最彻底地充实了这一概念:“任何参与可能对他人产生伤害的行动的人,即使是概率上的,都应该被要求暴露于某种伤害。”如果有人发出昂贵的信号,这意味着他们实际上拥有丰富的东西——技能、知识、资源、力量、智力、承诺——让他们能够承担这一成本。
这些人是你想雇佣的。这些人已经表明他们有足够的能力,愿意在其他领域受到打击。对于医生来说,这意味着他们有足够好的让病人健康并保持健康的记录,他们不需要通过拒绝教学来节省时间,或从与客户的关系中获得附带利益。江湖骗子只做不花钱的信号工作。昂贵的信号淘汰骗子。
这如何转化为数据科学?在与数据科学家的互动过程中,业务人员应该…什么?做出更好的决定?那太笼统了,没有意义。赚取更多利润?数据科学和利润之间有一个巨大的因果链——有太多的方式,其中一些是合法的,来解释未能执行。
一般来说,企业中的人们在与数据科学家的互动过程中,应该增加投入到只有人类才能做出的决策中的时间和资源的百分比。
在我看来,这是数据科学的核心目的。(当然不是唯一的一个,但是为了当前讨论的目的,我们可以将重点放在这个上)。有些人们花时间做的事情,机器也能做得一样好。还有一些人们花时间做的事情是机器根本做不到的。没有一个管理组织的人会用尽所有需要做出的决定。这意味着许多决策必然是仓促做出的,或者被推诿给不合格的下属,或者被遗忘,直到它们恶化到成为一个真正的问题。人们最终会关注他们不应该关注的事情,仅仅是因为他们不能足够快地筛选出吸引他们注意力的竞争需求。
数据科学的前景是,目前由人类做出的许多决定可以由算法做出,甚至更好。“更好”可以指更准确、更高效、更细致、更划算等。在自动化解放人们去做其他事情之前,自动化决策只不过是一种学术实践。决策自由是数据科学版的患者健康——持续保持和发展这种自由是数据科学家个人成功的基础。
鉴于道德准则必须由相对较少的人强加给大多数人,从伦理上定义道德准则的唯一方法是规定,不是道德,而是游戏中的皮肤。这极大地缩小了道德规范的范围,因为对成本的列举总是比对信仰的列举要少。道德准则不仅仅是定义游戏中的皮肤,它实际上破坏了道德行为,因为它为人们提供了不管能力如何都要发出美德信号的方式——使确保职业内的高道德标准变得更难,而不是更容易。
数据科学家不需要一个道德标准的列表。他们需要一系列方法来证明他们不是骗子。这将比其他任何事情都更能确保该行业的健康和可信度。
民主数据组织显然还没有结束创建道德准则的努力,但他们现在已经准备好的工作文件和我的谈话清楚地表明,他们专注于列举美德而不是成本。这是建立道德体系的错误基础。
通过无监督学习检验国际美食
Typical Tyrolean Käsespätzle (Image courtesy: https://austria-forum.org/)
像很多人一样,我也是一个美食迷。我非常幸运,在一个吃饭都是从零开始的家庭长大。我妈妈做饭,因为她从德国移民到美国,我接触了很多美味的德国菜肴。我最喜欢的一些包括 sespä tzle,Semmelknö del 和 Sauerbraten。虽然我从来不敢说我有妈妈的烹饪天赋,但我确实非常享受从零开始做一顿饭的过程,当然也和我自己的家人分享这顿饭。
以此作为我的背景故事,我认为开展一个涉及世界各地食谱的数据科学项目会非常有趣。我想看看我是否能了解世界各地不同烹饪之间的关系。为了探索这个话题,我收集了代表 25 种不同烹饪类型的 12,000 多种不同食谱的数据。然后,我进行自然语言处理(NLP ),将文本数据转换成可以输入机器学习算法的格式。最后,我进行了主成分分析(PCA)和主题建模,以获得数据的洞察力。
数据收集
我在这个项目中使用的食谱数据来自 Yummly 。我被授予了他们 API 的学生许可证(谢谢,美味!),这让我可以直接从 ipython 笔记本上查询和搜索食谱。Yummly 支持基于菜肴类型进行搜索。以下是支持的菜系列表:
- 美国、意大利、亚洲、墨西哥、南方和灵魂食物、法国、西南、烧烤、印度、中国、卡津和克里奥尔、英国、地中海、希腊、西班牙、德国、泰国、摩洛哥、爱尔兰、日本、古巴、夏威夷、瑞典、匈牙利、葡萄牙
我总共下载了 25 种受支持菜系的大约 500 种食谱。这导致了大约 12,500 种不同食谱。对于数据收集,我使用 Requests 库读入数据,并使用内置的 JSON 编码器将 JSON 数据转换成 python 字典。然后,将数据转换成熊猫数据框架相对简单。下面显示了数据框中几个选定行的屏幕截图。
Selected rows of the Yummly recipe DataFrame
对于我的分析,我只使用了对应于美食和配料的列。所有其他列都被忽略。
文本数据处理和机器学习工作流
由于数据只包含文本,因此有必要使用 NLP 技术实现一些预处理步骤。这些步骤如下:
- 复合某些成分(如橄榄油、玉米淀粉)
- 将成分拆分成单词列表的标记化
- 删除停用词和其他频繁出现的词(例如,盐、胡椒、水)
- 通过去掉单词的复数形式和其他后缀来词干化
- 单词包处理创建一个稀疏矩阵,该矩阵包含成分列表中的所有单词以及它们出现的频率
我用来实现前面步骤列表的工具包括 sklearn 中的 TfidfVectorizer 和 CountVectorizer。其中一些步骤,比如断字和停用词移除,需要我编写自己的代码来实现,因为这些步骤对于这个特定的用例来说更加独特。鼓励读者查看我为这个项目准备的 Github repo 来了解更多信息。
我重点研究的机器学习算法都是无监督学习算法。我做了 k-Means 聚类,看看我是否可以根据菜系类型将食谱聚类在一起,但是聚类对我的分析没有太大帮助,因为不清楚不同的聚类代表什么。相反,我将注意力集中在主成分分析(PCA)和潜在狄利克雷分配(LDA)上,我将在结果部分进一步讨论。
结果
为了能够可视化我的数据,我实施了降维,以便将特征空间从 1982 维(对应于我的数据集中不同成分的数量)减少到 2 维。这一步是使用主成分分析完成的,我保留了前两个主成分。然后,我针对第一个和第二个主成分为每个配方创建了一个散点图,如下所示。
A scatter plot consisting of all 12,492 recipes along the first and second principal components.
当在这个二维主成分空间中绘制所有食谱时,我没有学到很多东西,因为许多数据点是重叠的,所以很难在数据中看到任何结构。然而,通过根据菜系对食谱进行分组,并取前两个主成分的质心值,我可以在数据中看到一些有趣的结构。下图显示了这种情况。
A plot of the centroid values for each of the different cuisines along the first and second principal components. Group (A) is associated with Asian cuisines, (B) consists of Japanese and Hawaiian cuisines, © and (D) are European and American cuisines, respectively. Group (E) is a mixed bag of cuisines from all over the world including Cuban, Mexican, Indian, and Spanish.
上面的情节提供了一些关于不同菜系之间关系的有趣见解。我们可以观察到质心值倾向于根据相似的烹饪类型对食谱进行分组。例如,图 2 中的组(A)由中国、泰国和亚洲食物组成,它们都可以归类为亚洲食物。组(B)包括日本和夏威夷菜肴。这两种菜系都非常强调鱼,所以它们紧密地组合在一起是有道理的。©组完全由欧洲美食组成,如瑞典、法国和德国,而不远处的(D)组主要由北美美食组成。这些包括南方风味、烧烤风味和传统美国风味。最后,( E)组是世界各地许多不同美食的大杂烩。这包括古巴、墨西哥、印度、西班牙和西南部。当我想到这些美食时,我会想到大而大胆的味道,所以这些美食紧密地组合在一起是完全合理的。
读者可能会问,哪些特征(即成分)与第一和第二主成分联系最紧密?这可以在下图中看到。
A plot demonstrating the ingredients most strongly associated with the first and second principal components.
该图为两个主要成分中的每一个提供了主要特征的视觉表示。鸡肉、大蒜、洋葱和番茄等配料与第一部分的正向有很强的关联。这些风味与西班牙菜或印度菜有着紧密的联系。另一方面,鸡蛋、黄油、面粉、牛奶和糖沿着分量一的负方向具有很强的关联性。这些是法国菜或英国菜中常见的配料。同样,大豆、酱油和大米与第二部分的正方向有很强的关联。这些配料在亚洲菜肴中很常见。最后,奶酪、柠檬、橄榄油和西红柿与分量二的负方向有很强的关联。这些口味在意大利和希腊菜肴中很常见。该图有助于解释上图的结构,即当沿着第一和第二主成分作图时,为什么某些菜系在特定区域聚集在一起。
最后,我还运行了一个 LDA 模型,以便进行主题建模(见下文)。我很好奇,想看看是否有可能根据不同的食材所属的菜系来区分它们。我将主题数量指定为 25,因为我知道在我的数据集中有 25 种不同的菜系。然而,LDA 的结果有点混乱。在某些情况下,LDA 主题是特定的美食,如意大利菜或泰国菜。然而,有些主题是不同种类的菜肴,如甜点、酱汁,甚至鸡尾酒。虽然这个结果不是我想要的,但现在回想起来,它确实非常有意义。LDA 是一种机器学习技术,它可以识别频繁出现的单词组。因此,在超过 12,000 个食谱的语料库中,与烹饪类型相比,基于菜肴类型(即甜点、汤、沙拉或酱)的单词组可能有更强的关联。
Results from LDA topic modeling.
结束语
我在探索食谱数据集的过程中获得了很多乐趣,因为我真的很喜欢将我对食物的热爱与我在数据科学中获得的新技能结合起来。人们也可以为这种类型的分析做一个强有力的商业案例,因为这些信息可以用来向 Yummly 平台的用户提供食谱建议。例如,真正喜欢烧烤食物的人也可能真正喜欢葡萄牙菜,因为当沿着第一和第二主成分绘制它们的质心时,这两种菜类型重叠。在没有研究数据的情况下,我无法预见这样的关系。
然而,重要的是要披露,这篇博客中使用的数据并非没有缺陷。例如,Yummly 基本上是一个从许多其他食谱网站或博客上收集食谱的网站,所有这些网站都是英文的。因此,这些食谱中的许多可能是美国人对其他烹饪类型的吸收。我敢肯定,我的意大利朋友会说,像 this 这样结合了鸡肉和香蒜酱的食谱“不是意大利的!”,尽管 Yummly 把这个食谱贴上了意大利的标签。处理这个问题的更好的方法可能是找到他们母语的食谱,并使用一些先进的翻译算法将它们翻译成英语。然而,由于成分可能因特定地理区域而异,这也可能导致意想不到的问题。例如,某些配料可能没有英语对等词(例如,意大利帕尔玛火腿、帕尔玛意大利干酪),因此这些配料将总是与意大利菜肴联系在一起。这可能会导致不同类型的菜肴之间的相似性减少,但我认为这仍然是值得探索的。
非常感谢你阅读我的博文。如果你想为这个项目探索我的代码,你可以在下面的 Github repo 找到它。此外,反馈将非常感谢,所以请随时联系我或给我一个“鼓掌”,如果你喜欢这篇文章。
一个影响机器学习的实验性开发过程
如何避免生产道路上的常见错误
Photo by Alex Eckermann on Unsplash
利用机器学习来提供切实的用户价值,这真的很难建立产品功能和内部运营工具。不仅仅是因为很难处理数据(确实如此),或者是因为人工智能有许多琐碎的用途,虽然很简洁,但并不那么有用(确实有),而是因为几乎从来没有这样的情况,你会有一个明确定义和限定的问题交给你,而且在纯粹的技术方面之外还有许多未知因素可能在任何时候破坏你的项目。我看到了许多关于技术方面的优秀文章,提供了如何处理数据和建立机器学习模型的代码和建议,以及如何雇用工程师和科学家的人员方面,但这只是一部分。另一部分是如何引导技术和人通过障碍,让这种工作产生影响。
幸运的是,我已经因为许多原因多次未能部署人工智能来提供商业和用户价值,并看到从初创公司到财富 500 强数据科学和研究小组的朋友和同事都在为同样的问题而挣扎。技术几乎总是有价值的,人也是有能力的,但是造成差异的是人们如何一起工作,他们在研究什么技术。换句话说,我相信你能雇佣到技术过硬的人,他们能很好地应用他们的工具,但是除非是合适的人在合适的时间为合适的业务问题构建合适的东西,否则这些都没用。(是的,没错,但这比你想象的要难)。
在这篇文章中,我试图巩固学到的知识,引用我认为有用的现有文章(对我肯定错过的参考文献表示歉意),并为添加一些色彩,说明为什么构建实验产品很难,它与其他工程有什么不同,你的过程可能会是什么样子,以及你可能会在哪里遇到失败点。
警告:对于这种动态的任何东西,您都需要使有意义的部分适应您自己的需求,并不断地发展它,但是大多数基本构件应该是适用的。这里也没什么聪明的,大多是常识,但是很多事情回想起来就是那样。我试图做的是将事情放入一个支持实验性开发过程的概念框架中——建立一个问题,定义指标,构建一些简单的端到端的东西,可以学习和迭代——这在理论上看似相似,但在实践中与许多开发人员、经理,特别是 R&D 以外的人所习惯的完全不同。
为什么是实验性开发过程
在我们最熟悉的软件工程中,我们从一组需要作为软件实现的特定产品或业务需求开始开发。当前流行的基于小特性的 sprints 的增量构建项目的过程已经建立,并且对于围绕可预测性而不是围绕实验的构建非常有用。它们是为构建软件而优化的,我们可能会发明产品功能,但不是技术——所以我们知道它会工作,并且可以估计大概需要多长时间。这绝非易事。仍然存在不确定性、软件和设计约束以及成本/时间/性能权衡,但是这些权衡以及处理它们的方法更容易理解。在高层次上,这些对于实验开发是有意义的,但是当应用时,它们经常导致失败,因为很难估计一个实验会在什么时候进行得有多好。
另一方面,当我们开始对人工智能功能进行产品开发,或者优化内部业务流程时,我们有许多未决问题,主要是:
- 问什么问题才是正确的?
- 解决这个问题的最佳方案是什么?
- 我们有这方面的数据吗?
- 效果如何?
- 我们能在多大程度上将其投入生产?
除了软件和设计的限制,我们对问题和解决方案本身有不确定性。因此,这些产品具有很高的实验和操作风险。实验风险之所以存在,是因为根据定义,有许多未知因素。这种风险存在于用户双方(即问题,他们实际上想要什么?)和技术方面(即解决方案,我们实际上能构建什么?),两者紧密相连。
当我们试验我们可以构建什么以及它的性能如何时,我们改变了我们对它如何最好地解决用户问题的理解,并进化出正确的问题。
因此,这些特性几乎从来没有一个明确的终点,每一次迭代都创造了对问题的进一步理解,并开启了新的解决方案。
运营风险与任何项目一样存在,并随着实验过程而增加,因为时间表的不确定性和迭代解决方案会导致更长的项目生命周期和错过交付机会。按照可预测的时间表建立一个实验项目是非常困难的。
事实上,不管你在上面花了多少时间,期望第一次(多次)构建错误是有用的。这意味着你不会完全理解每件事,也不会在做任何事情之前就把它做好。数据产品有一个自然的进化过程:指导原则是我们有一个合理的、可行的但不完美的解决方案,我们用它来收集更多的知识来改进解决方案。
失败
一些最常见的实验和操作故障如下。不管你做什么,不要期望完全避免它们,但是你绝对可以学会减少它们。
**没有找到解决方案。**不是因为它被测试了,失败了,项目被关闭了,这很好,但是陷入了一个没有尽头的问题的迭代中。这通常是因为没有建立用户成功标准,或者没有一个项目涉众频繁参与的进度跟踪机制。
构建尚未量产的解决方案。这通常伴随着早期没有足够的组织或利益相关者的支持,没有建立工程部署可行性或考虑基础设施的成本,或技术转移和维护的时间,或开始采用过于复杂的解决方案,这无法从原型进行转移。
**产生一个解决方案,但不对其进行迭代。**这通常伴随着缺乏用于重建和重新部署解决方案的适当基础设施,以及收集反馈和监控指标的必要数据管道。
构建无用的解决方案。一切顺利,但仍是一次失败,可以说是浪费了更多时间以来最糟糕的一次。发生这种情况的原因有很多,但最常见的是,从一开始就没有与风险承担者就有影响且可操作的问题达成一致,从而解决了对业务价值不明确的问题。
我们都多次经历过这些失败,这对每个人来说都是令人沮丧的。
你认为你浪费了时间,觉得你没有能力产生影响,利益相关者认为你没有与用户共情,不理解业务问题,工程不知道你在做什么,认为你什么也没做,而与此同时,公司的其他人都在努力赶上最后期限。随着时间的推移,这导致了可信度的丧失,并质疑如果实验项目最终没有真正解决商业问题,它们会带来什么价值。
实验开发阶段
没有人喜欢为了过程而过程;它们增加了开销,当执行得不好时(这是很可能的)它们会占用原本有效率的时间。但是,除非你是独自工作,即使这样,你也需要一些组织结构。一个过程只有在帮助你实现一个结果的范围内才是好的,所以确保这个过程不是目标,并且尽可能的轻量级。
我们关注的最终结果是解决业务或用户需求。但是让我们把它分解成中间结果:
- 识别有影响的业务问题
- 快速解决问题
- 学习并迭代它
对于所有这些结果来说,成功的最重要的预测因素是确保每个参与者都理解并同意解决什么问题以及如何解决(在企业中:你需要与利益相关者保持一致)。开始时不只是一次,而是随着事情不可避免地不断出现。因为这需要你、产品、设计、工程和任何其他业务利益相关者之间持续和积极的沟通,下面的大部分内容实际上只是为了方便你在正确的时间与某人交谈,告诉他们他们需要知道什么,并从他们那里获得你需要知道的信息(我们稍后将讨论什么是什么是,但现在请注意后者不是被动的“他们告诉你”,而是你收集信息的主动努力)。
您需要这种交流来洞察业务或用户需求,以识别具有明确影响的具体、有价值的问题;当您对结果的质量进行反馈循环试验,以告知方向和验收,对解决方案的可行性进行工程评估;用于数据收集和管道的资源,监测绩效并为模型建立反馈回路;和生产解决方案的基础设施资源的能力。
这可以分为以下几个主要阶段— 构思、实验、数据收集、原型制作、技术转让、和监控 — ,如下图所示。我将假设您在一个主要利益相关者是产品和工程团队的环境中工作,但是即使名称改变了,大多数仍然适用。
Experimental development stages, assuming research, product, and engineering organization. Product and engineering stages may differ, but should have a closely corresponding one. The light pink box is dominated by your experimentation, the light purple by the user problem and solution, and the light green by engineering. Gray boxes represent expected output from each stage.
这些阶段实际上几乎从来不是连续的;而是来回往复,尤其是在构思、数据收集和原型制作之间。过程中的每个阶段至少有四个可定义的组成部分:目标、预期输入、预期输出和估计时间表。在高层次上,这些组件在项目之间是一致的,但是每个组件的确切实例化将因项目而异。
保持过程透明和负责任的一个关键是为每个项目维护一个共享的文档,当你经历每个阶段时,你在文档中定义组件是什么。此外,维护一个共享文档是一个好主意,该文档包含您在开发的所有阶段正在进行的所有项目的最新状态,包括您可以指出的以前的成功,以及以前的失败和失败的地方。
下表总结了我们在每个阶段努力实现的目标以及需要注意的事项;本文档的其余部分将详细描述其中的每一项。
思维能力
整个过程的第一步,也是最重要的一步是确立你正在解决的问题。这应该是一个具体的用户问题——定义不清、结构不良的问题很难解决(如果我们不知道我们想要什么,我们怎么知道我们什么时候解决了它?).它还应该对业务有很高的可衡量价值——增加客户或收入,例如提供新产品、打开新市场、提高客户满意度;或者节约成本,例如通过优化或自动化部分工作流程。
几乎每一次关于商业中的数据科学或机器学习的讨论都将从与业务保持一致开始。几乎不可能夸大这一点的重要性。识别问题的两大策略是:a)在内部提出问题,并向利益相关者“推销”解决方案;b)利益相关者带着问题来找你。实际上,它通常比这两者中的任何一个都更具协作性,但是只要存在区别, b)更有可能暴露业务的有价值的问题,为利益相关者创造一种一致感,这将导致对你的责任,并有助于防止陷入困境;简而言之,解决方案更有可能被建立并产生影响。
你总有一天会想,这很诱人,也是不可避免的,但我有一个很棒的新想法,是没人想过的。不幸的是,如果你不付出额外的努力来获得认同,这也很难成为一个成功的故事(不过不用担心,通过反复提供有价值的东西来建立信任后,你可能最终会成功)。如果这是你想走的路,那么一定要关注为什么你要构建的东西会提供价值,而不是你如何构建或构建什么。
那么,你怎么知道你得到了正确的问题呢?这不仅是好的,而且是必要的,在这里,你强烈地质疑利益相关者所提议的商业价值。这可能很棘手,因为许多人不习惯将对一个想法的智力挑战与对他们自己或他们角色的挑战分开,尤其是在不同的组织层面。你也不想让他们失望,减少他们与你一起工作的热情,因为他们可能会觉得奇怪,你没有抓住机会应用你的技能,但每个人都受益于尽早建立支持或反对一个项目的最强有力的理由。这需要一些实践,但是预先明确这一点,然后友好地协作实现业务价值是非常值得的。
我们都热衷于取悦他人,使用我们强大的机器学习锤子,大多数事情看起来像钉子,所以确保应用机器学习是有意义的(我们将在后面的阶段更多地讨论这一点,但即使在这一点上,这也将节省您的时间)。它可能会引起一些轰动,这可能是你为什么要这么做的原因,但是如果还没有什么东西,机器学习不会神奇地使一个功能变得有价值,即使你能做到,也不是每个问题都需要人工智能。如果问题可以用简单的算法解决,做简单的事情,可能会伤害你的自尊心,但会有助于你的机会。现在传递一个项目比在所有工作完成后让它无用要容易得多。
另一个关键是确保你和利益相关者理解 **用户行为将如何基于提供的输出和分析而改变,和需要什么样的改变管理来实现。**即使你能肯定地回答随后的技术问题(即,你有数据,能建立模型,能扩展它),如果基于提供的输出没有行动的改变(可能因为它解决了错误的问题,或者因为输出不重要或不可变),或者改变需要重大的改变管理(因此可能缺乏采用),那么你没有增加价值。
在确定时机是否正确时,要考虑的有用方面是您对每个阶段的失败风险和所涉及的工作、资源依赖性(包括基础架构和数据)以及交付机制的复杂性的诚实猜测。我们将依次讨论这些问题。
一个好的起点是具有高影响值的问题(输出将导致用户操作),增强现有的用户工作流(不需要他们改变它),并且具有低风险和低工作量(可用数据、快速原型化的能力、高性能算法、简单的交付机制)。
你能满足的越多,你就越有可能得到相对快速的价值证明。从这些类型的问题入手有助于建立利益相关者和你对自己实现价值的能力的信心,从而建立解决高风险问题所必需的信任。
这并不意味着你不应该处理事情不清楚的复杂问题,当然你应该这样做。我们大多数人都喜欢挑战没有简单答案的问题,只有在坚持克服困难并创造出新颖的解决方案后,我们才能宣布胜利(这就是我们做我们所做的事情的动力,对吗?).这些会带来更多的个人成长和成就感,有时解决它们会创造巨大的价值。这就是它们如此吸引人的原因。
然而,你有限制(你有限的带宽,你公司有限的跑道,等等。),所以你不能做所有的事情,当优先处理哪个问题时,注意不要把困难(问题的困难和解决问题需要经历的困难)和价值等同起来。困难的问题通常不是最大的业务影响所在。它通常存在于更简单的问题中(或者至少是可以被重构或分解成简单问题的问题)。所以承担难题(活一点!),就尽量简单点他们。
如果你发现自己花了大量的时间来反对某事(例如,说服利益相关者产出是有价值的,对基础设施征税),这是一个好迹象,现在不是时候。有时候很难放下这些,尤其是如果你坚信它的价值,但是现在可以更好地利用这些时间。
帮助利益相关者识别这些类型的问题将增加你的工作产生影响的可能性(这是上面的 a)和 b)之间的区别变得模糊的地方)。对很多(大多数?)人工智能和机器学习仍然是未知的,他们不明白为什么预测 A 目前是不可能的,但重新构建问题以预测 B——看起来很像 A——是超级容易的,或者如何通过求解 B,你可以实际上给他们 C,D 和 E,这对他们来说实际上可能更有价值。你需要弥合这些差距。
即使有人要求你建立一个特定的机器学习解决方案,开一个头脑风暴会议并回到问题上来也是有益的。不要太专业,但要提出问题的不同框架,以及解决问题的不同方法的例子。
有些问题的表述可能等同于利益相关者或用户,但你知道可能会把它从不可能变得容易。真正理解问题是什么取决于你(换句话说:帮助别人理解他们的问题取决于你)。
出现问题后,下一个最重要的步骤是确定评估指标和成功标准;知道衡量什么和如何评估表现意味着你知道你解决问题的情况如何(以及你什么时候足够好,或者不够好)。重要的是,尽管这包括实验模型评估指标(如准确性、召回率),但它实际上是关于与利益相关者在用户或业务成功标准上保持一致。评估标准是你优化的,成功标准是利益相关者或用户关心的,它们不可能是相同的。例如,通常不清楚召回率提高 5%或日志丢失率下降会如何影响用户体验,可能会,也可能不会,因此重要的是将其转化为对用户或企业的实际价值(例如转换率、保存的项目数量、执行一项操作所花费的时间、售出的额外座位数量)。在开始之前,尽最大努力就目标成功标准达成一致,否则它可能会在以后移动目标(并且是陷入无休止优化的促成因素)。
许多用户纠结的一个方面是,无论你做什么,模型都会出错——会有一个错误率。一些应用程序,如电影推荐,可能是宽容的,而其他应用程序,如医疗诊断,则具有更高的风险,即使一个错误也可能完全降低用户对模型的信任。确保你和利益相关者明白错误的代价是什么。开发高风险的应用程序会增加失败的几率,但这并不意味着应该避免失败。相反,尝试通过将问题分解成更简单的部分来重新构建问题,这样输出可能是部分正确的,或者即使有错误也仍然提供一些有价值的东西。
把错误率放在现状的背景下也是有用的。有时候,人们倾向于低估他们现在所做的任何事情中的错误,你向他们暴露了一直存在但从未量化的错误。不管是好是坏,现在它们与你的模型联系在一起了。无论您是考虑到一个否则不可能实现的功能,还是自动化以前的手动任务,如果您可以表明某些方面已经改进,即使它并不完美(例如,与人类相似的准确性,但快得多),您就不需要太担心模型错误。
**一旦你确定这是一个重要的问题,有了一个已知的成功标准,你就可以**提出一个解决方案。这个解决方案就像一个假设,会做出很多假设:
- 你有数据/资源
- 你知道如何将问题模型化并建立一个足够好的系统
- 这个系统将会扩展
- 这在技术上是可行的,而且公司现在也负担得起
为了给你的利益相关者设定正确的期望,分解问题是很重要的,解释为什么和哪些部分是困难的(不要太专业),假设在哪里,风险和限制可能在哪里。
这些假设中的每一个都将在项目的下一阶段得到测试和验证,但是每一个都将不可避免地在某些方面出错。这完全没问题,并期望遵循你的直觉,并作出简化的假设。实验项目通常试图解决一个困难的问题,通常在你试图解决的问题开始时没有很好的理解。随着事情的拖延,不熟悉这个过程的人很容易失去热情或信心。你需要成为项目的冠军,相信它会起作用并带来价值,并在接下来的阶段全程指导它。
数据收集
有了问题和建议的解决方案,您需要确定您是否有数据资源来构建它。关于数据收集已经写了很多有用的建议,所以我就简单说一下。这是大多数项目很快遇到问题的地方。
每一个数据产品都依赖于可用的、干净的和可靠的数据来构建,而这几乎是不可能的。数据确实是丢失的、有噪声的和不可靠的。任何机器学习项目的大量时间都将花费在聚集和清理数据上。这通常也是最大性能提升的来源。清理和准备数据并不在问题之外,在很大程度上,这才是问题所在。
这里经常需要根据可用的数据来迭代和重构用户问题。请注意,我们并不是从检查我们的数据并询问我们可以用它来解决什么问题开始,而是一旦我们手头有了问题,我们有什么数据可以用来解决它。当你理解你的数据的局限性,并重新定义问题时,向利益相关者保证这不会从根本上改变价值是很重要的。
在大多数情况下,数据可能部分可用,但不完整,或者结构不良,因为它不是在您的解决方案中收集的。在其他情况下,数据可能根本不存在,您必须收集数据。这包括在野外找到它,自动收集它(从用户或您的应用程序外部),手动创建它,或众包。如果数据需要作为具有某种形式的不存在的类标签的训练集,这包括自动引导标签或手动标注。
在这一点上,较小的样本量和一些噪声是没问题的。试着收集足够多的数据,这些数据有一定的代表性,但不会成为负担,并且足够干净,这样就不会有明显的错误。除此之外,现在花太多时间在这里会有收益递减。
作为跑步主题,不要过早优化。无论你在这里花多少时间,你都可以花更多的时间。只是在进入其他阶段之前,你不应该在这里花太多时间,因为你知道你将使用这些数据。我见过双方都不顺利。一方面,太少或杂乱的数据被带入原型,结果证明不具有代表性。另一方面,在得到一个原型之前,大量的时间花费在收集和等待越来越多的数据上。在前一种情况下,您可以返回并在现有模型中包含更多数据,并立即衡量改进。在后一种情况下,谁知道在什么时候这就足够了。
在这一点上,您应该提前考虑是否可以创建健壮的数据管道,并在需要时监控数据质量,但是不要担心实际上是否会这样做(我们稍后会担心这一点)。
样机研究
这个阶段是实验性开发过程的必要性的主要原因。按照固定的时间表进行实验几乎是不可能的;可能需要一天来训练模型,意识到它不起作用,实现新功能,获得新数据,重新训练,重复…在意识到它不起作用之前需要一周或一个月。
这导致了实验和在路线图上或在内部按计划交付特性之间的自然和不可避免的冲突。追踪兔子洞很容易:尝试新数据、新模型、新特性、新架构等。它让你感到忙碌和富有成效,但你可能实际上并没有完成多少。在没有明确的用户问题或良好定义的成功标准的情况下尤其如此。最重要的目标是尽快达到你知道如何解决问题或者意识到你不能解决问题的程度。
过程的挑战是创造一种平衡:在确定你何时进入一个没有产出的兔子洞的同时,为探索创造空间。
你需要一个机制来设定目标,并在一个小的时间范围内监控你的进展,通常是每周一次。您可以创建一个按优先顺序排列的实验路线图,在内部记录结果,并经常向适当的利益相关者报告成功标准的性能变化,以验证如何继续进行。每周你都应该更新你对成功可能性的直觉,用证据表明你要么前进,要么停滞不前。
一个很好的论坛是正在进行的项目的演示,中间结果显示进展。在这里感到不舒服是很自然的,因为你知道这些早期的原型并不完美,你可能仍然有许多未解决的问题,但好处是创建了一个迫使项目达到中间完成点的功能,思考设计和 UX,在早期获得利益相关者的反馈并改变方向,最后,创造对可能性和进展感的兴奋。如果你等到所有的事情都整理好了才与利益相关者分享,你已经为失败做好了准备。
构建简单、可工作的原型并在外部共享是一种很好的方式,可以消除项目风险,确保核心价值,激发潜在价值,尽早暴露问题,增加认同,并保持势头。
我想强调尽早考虑 UX 和设计的重要性。你可能会看到一个带有一些例子或混淆矩阵的终端输出,并想象几个假设的显示(图表、列表、百分比、数字等)。).因为你知道它们的信息内容是同构的,所以如何可视化对你来说并不重要。您的利益相关者可能无法完成同样的转换。他们会被具体的例子和展示方式所吸引。这将限制更广泛的信息,使他们看不到产出的潜在价值。
作为正在进行的演示的一部分,不要仅仅展示终端输出;提供几个例子和模拟输出的变化。例如,一些人喜欢看数字(主要是你),一些人喜欢看视觉效果(你仍然需要做认知工作),而一些人只是想要直接给他们的洞察力。几乎没有人知道如何处理一个概率。
通常,一个好的起点是将输出浓缩成非常简单的东西,比如评级或标签。令人惊讶的是(但不是真的),大多数人不像我们一样喜欢看数字。这可能会改变您对问题建模的方式,或者只是改变输出呈现给用户的方式。
您很少不能相当快地构建出至少一个工作得不错的简单解决方案(至少作为基线)。在实践中,我发现在这之后陷入兔子洞的可能性很高,因为你过早地开始优化,试图改善评估指标性能,而不知道当前的解决方案对于用户成功标准是否足够好。我知道你可以做得更好,但这是一个很好的理由,让你的解决方案迅速呈现在人们面前,这样你就有东西可以交付,即使你在努力改进它。
其中很重要的一部分是,它是否能达到所需的质量水平。不管解决方案有多简单或复杂,不管是基于规则的还是深度学习的模型,大多数解决方案都会有所作为。我发现这在概念上是人们的一个主要症结,尤其是那些习惯于传统软件工程的人,在传统软件工程中,更明显的是验证功能是否按预期工作(软件运行或不运行)。
**从某种意义上说,这个模型是可行的,因为它会给出一个预测,或者一个建议,或者一些结果。问题是**评估结果集对于解决用户问题是否可接受。请注意,这并不等同于提高您的模型评估指标的性能,除非它们碰巧是相同的(这是非常不可能的)。这就是为什么有一个既定的成功标准和评估工具来衡量绩效是如此重要。
如果您不知道您的评估指标如何影响您的用户成功(您可能不知道),您就没有办法确定正确的召回-精度权衡,或者日志损失或准确性阈值,并且您可以愉快地继续迭代和判断它是否足够好。
尤其是在实验评估与用户指标关系不确定的时候,你需要尽可能保持解决方案的简单,这样你就可以更快地把它提供给最终用户,并开始测试这种关系。
因此,您一直在努力,但算法并不奏效,或者尽管实验指标趋于稳定,但您可能无法实现满足用户需求的性能,或者数据并不像您认为的那样具有代表性,或者突破就在眼前,或者这只是一个不正确的问题。
你如何知道何时停止寻求一个没有结果的解决方案?这是这个过程中最难的部分之一;没有好的答案,所以这应该随着你的边做边学而不断迭代(说实话,我还不知道有谁做到了)。因为它也是最不稳定的,它需要仔细的关注和执行(让关心这个问题的利益相关者对你负责是一个好的开始)。
一个自然的时间是在建议的一系列实验和直觉已经用尽之后,成功的可能性似乎很低。在这里,你要评估继续(包括你没有解决的其他问题)的机会成本和成功的收益。
对解决方案的探索通常会导致问题的修改——你会意识到你不能很好地解决最初提出的问题,但可以解决一个密切相关的问题。因为你不知道你最终要解决哪个问题,所以你不应该过早地优化第一个解决方案:它不应该按比例构建,模型也不需要优化。在这一点上,你需要为迭代概念验证原型进行优化。
也就是说,虽然不是为可伸缩性而构建,但你应该提前考虑可伸缩性,并且找到最简单的算法,在大规模下运行。
从一开始就有增加复杂性的自然愿望,尝试最新的算法或包,打破以前的一切,或构建一些新颖的东西,但请记住,你是为自己做这些,用户不关心你如何做,只要它能工作。最重要的一点是要有一个可以用来收集反馈的功能系统。例如,线性模型在开始时可能工作得很好,您可以在以后根据需要增加算法的复杂性。越简单越好。
解决方案越复杂,启动所需的资源(计算、工程、维护)就越多,出错的地方也就越多。如果你从复杂的东西开始,你可能在原型上花费了太多的时间,降低了部署成功的可能性,所以在技术转移失败后,你可能会回到原型上。你也不需要自己构建所有的东西。使用现有的开源工具通常能让你达到目的。现在你可以把节省下来的时间用来为自己解决那个难题:)。
技术转让
一旦你建立并验证了一个功能系统,剩下的主要问题就是生产模型。
这通常是我们的术语给我们带来麻烦的地方——你说你已经“完成”了原型设计,涉众很容易听到“已经准备好了”,或者假设因为你已经消除了许多不确定性和未知性,问题变得更接近于软件工程,现在应该很容易或很快。
尽管经历了前几个阶段的不确定性,发现如何生产解决方案同样困难,如果不是更困难的话。
原型不能成功转移到生产中有许多原因。一个是缩放。在你的 Jupyter 笔记本上让一些东西足够有效地离线工作是一回事,在那里你从多个来源提取并连接数据以形成一个更小的数据集。在生产中实时做同样的事情,同时合并来自多个来源的不断变化的数据流是另一个问题。由于特征计算的计算复杂性或算法的复杂性,在更多的数据上它可能在计算上不可行。或者对数据是如何进来的或者它包含什么做出了某些假设,这些假设在生产中不成立,并且需要在数据管道上进行大量投资,以验证、标准化、合并和提供您需要的所有数据。
因此,生产化通常需要简化和优化模型,并使其足够健壮和容错,以满足生产中所需的服务级别协议和服务质量。它还需要成本高昂或难以维护的基础设施,以及来自工程团队的资源分配,以进行集成,从而在产品路线图上进行集成。
随着对产品和工程以及业务中其他地方所需资源的不断需求,通常具有更直接或更具体的商业价值,早期调整对于成功的技术转让是绝对必要的,尽管这还不够。
一个相关的问题有点循环,因为你经常需要证明一个解决方案将带来的价值,以便为生产优先考虑它,但是你需要部署一个解决方案,甚至可能迭代它来收集数据以显示它的价值。与原型类似,生产交付机制越简单,就越有可能成功集成。换句话说,价值的举证责任与交付成本相关,当价值不确定时,有一个损失成本的交付增加了开发的可能性。
降低交付成本的最简单方法是降低您的计算需求。在建立模型的过程中,我们有一种自然的倾向,即尽可能建立最好的模型——引入更多的数据,生成更多的特征,组合更多的算法。这是相反的练习。您是否需要合并多个数据流,或者仅用一个数据流就能满足用户指标?需要在线运行,还是一天一次批量计算就够了?即使当你在计算上投入了足够的钱,你的模型或特征计算是平凡的并行化,它也是值得简化的,原因我们将在下面讨论。
你也可以从提供商业运作的内部工具或者狗粮功能开始。由于这些通常可以通过简单的交付机制来完成,如传递 csv 或笔记本,并且不需要像面向用户的产品功能一样完善,因此这是一种在不涉及许多其他组织资源的情况下建立价值的好方法。您也可以尝试利用现有的交付机制,如 Elasticsearch,或任何您已经使用的搜索引擎,将您的输出编入其中,从而允许它与现有数据一起立即提供。
最后,争取可重复的交付过程。首先,这些特性的部署可能是一次性的或临时的,您可以创建一个服务或库,并定制管道。不仅每一个都要花费大量的时间,而且随着你启动其中的几个,维护成本将开始增加,数据管道将变得繁琐。当您通过几个成功案例在您的组织内建立势头时,您应该开始考虑如何创建一个更强大的工程管道来交付这些功能,从而允许您插入新模型并使它们相对容易地可用。最终你的目标将是重用和链接你的工作的能力。通过缓存某些数据或模型工件,创建公共特性存储,并拥有健壮的 API,您不仅能够更可靠地交付到产品中,而且能够更好地完成新项目的数据收集和原型制作的早期阶段(谁不喜欢复合回报呢?).
生产的一个有时被忽视的非技术方面是支持走向市场的努力。即使涉及的变更管理很少,也需要对用户群进行培训。到目前为止,利益相关者希望知道没有机器学习解决方案是完美的,但用户可能不是,最终你的成功取决于他们。如果他们没有发现一个有价值的特性,即使涉众发现了,这仍然是一个失败(对于一个内部工具,涉众可能只是你的用户的一个子集,对于一个产品特性,内部涉众不是用户)。
用户可能不需要知道或理解内部工作方式、错误率等。以及内部利益相关者,但你可以肯定他们会有问题。您可能不直接负责处理这些问题,但是为那些负责的人提供资源确实非常有用,可以确保用户期望设置正确,并且对特性功能的误解和误解最小化('他们认为这做了什么?‘听着耳熟吗?).您可以通过为用户编写内容来提供帮助,因为您可能会得到同一个问题的大部分内容,即 FAQ。**
监视
**我们已经把它投入生产了,我们做得对吗?!不完全是。现在你需要**监控你的模型的性能,并根据需要进行更新。如果做不到这一点,我会想起刘易斯·卡罗尔的名言——“在这里,我们必须尽可能跑得快,只是为了留在原地。”如果你不更新你的模型,它最终会变得陈旧,当它更新时,依赖它的用户会开始注意到有问题,它的价值会下降。
这个阶段是构建一个简单的解决方案并快速部署它的关键合作伙伴。在一个解决方案被生产之前,需要有一个关于如何监控和维护结果质量的计划,如何更新模型,以及从用户那里收集关于系统性能的反馈,以查看它产生了什么影响,以及它是否实现了预期的用户价值。
不管你的训练和测试数据是多么有代表性和精心构建的,一旦实时数据流动,你就会发现新的问题(你没有在数据收集上花太多时间,对吗?).希望你的模型能够容忍大多数错误,但是就像软件中的任何错误一样,当它们发生时,你需要注意。您最初建模的底层数据分布也可能会随时间漂移,导致您的模型以比彻底失败更微妙的方式出现问题。
您可以通过收集新数据、更新训练集和测试集以及定期重新训练模型来解决这些问题。机器学习功能需要反馈循环来提高我们对用户、数据和解决方案质量的理解。首先,您可以有一个简单的反馈循环,收集数据,然后用于手动重新触发再训练,以更新模型的单个版本。只要你能向涉众和用户清晰地表达出如何维护模型的计划,那就很好。
最终,当您理解了重新计算的必要时间表时,您可能希望模型在线更新,或者维护所有内容的版本—模型、训练和测试集—以获得可再现性和在出错时回滚的能力。其中每一个都引入了进一步的工程复杂性,除非必要,否则应该避免。但是随着您的成熟和创建更加健壮的管道,版本控制和监控将成为不可或缺的一部分。
结束语
机器学习和其他人工智能技术非常强大,当一切正常时,你可以利用它们产生巨大的影响。作为一名研究人员,探索困难的问题和算法很有趣,但要让人工智能解决方案变得有用,除了建立一个优化良好的模型之外,还有很多事情要做。你已经注意到了主题:经常与人交谈,并真正确保你们相互理解,提前计划下一阶段的要求,这样你就不会对它们感到惊讶,但不要过早陷入困境,尽可能长时间地让所有技术上的事情尽可能简单。
尽管有最好的计划,你和我仍然会失败?).没有一个过程能把你从这种困境中拯救出来,但是通过一些结构来帮助你浏览常见的陷阱,至少你可以学会减少它们。一旦你完成了几个项目,它就会变得越来越自然,一旦实验性的工作开始成为你的组织文化的一部分(文化是什么,除了一套共享的仪式?),那你就真的会飞了。
进一步阅读
[1] Domino Data Labs 管理数据科学 ( Strata 讲座,博客 )
涵盖大量挑战并呈现类似生命周期的优秀指南。
[2] IBM 的 CRISP-DM
[3] 管理数据科学的科学
[4] 生产 ML 系统的挑战
[5] 人工智能需求层次
[6] 管理 DS 是不一样的
[7] 关于 DS 管理的思考
[8] 人工智能启动指南
【9】DS 工作流程
[10] 机器学习模型开发的最佳实践方法
[11] 数据科学过程被重新发现
[12] 构建有价值的数据科学项目
[13] 数据科学中最难的事情:政治
[15] 数据科学敏捷周期
[16] 敏捷数据科学的最佳技巧
建议,批评,笑话?我很想听听。任何你认为有用的额外阅读,让我知道,我会添加它们。
下腰痛的探索性数据分析
Image collected from https://unsplash.com/photos/XNRHhomhRU4
腰痛 ,也叫腰痛,不是一种病症。这是几种不同类型的医学问题的症状。它通常由下背部的一个或多个部位的问题引起,例如:
- 韧带
- 肌肉
- 神经紧张
- 构成脊柱的骨结构,称为椎体或椎骨
腰部疼痛也可能是由于附近器官(如肾脏)的问题引起的。
在这个 EDA 中,我将使用下背部疼痛症状数据集并尝试找出该数据集的有趣见解。我们开始吧!
数据集描述
数据集包含:
- 310 观察结果
- 12 特征
- 1 标签
导入必要的包:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inlineimport seaborn as sns
sns.set()
from sklearn.preprocessing import MinMaxScaler, StandardScaler, LabelEncoder
from sklearn.svm import SVC
from sklearn.naive_bayes import GaussianNB
from sklearn.linear_model import LogisticRegression
from xgboost import XGBClassifier, plot_importance
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score,confusion_matrix
读取.csv
文件:
dataset = pd.read_csv("../input/Dataset_spine.csv")
查看数据集中的前 5 行:
dataset.head() # this will return top 5 rows
移除虚拟列:
# This command will remove the last column from our dataset.
del dataset["Unnamed: 13"]
数据集摘要:
DataFrame.describe() 方法生成描述性统计数据,这些统计数据总结了数据集分布的集中趋势、离散度和形状,不包括NaN
值。这个方法告诉我们关于数据集的很多事情。重要的一点是describe()
方法只处理数值。它不适用于任何分类值。
现在,让我们来理解由describe()
方法生成的统计数据:
count
告诉我们一个特征中的NoN-empty
行数。mean
告诉我们该特征的平均值。std
告诉我们该特征的标准偏差值。min
告诉我们该特性的最小值。25%
、50%
和75%
是每个特征的百分位数/四分位数。这种四分位数信息有助于我们发现异常值。max
告诉我们该特性的最大值。
dataset.describe()
dataset.describe() method output
重命名列以增加可读性:
dataset.rename(columns = {
"Col1" : "pelvic_incidence",
"Col2" : "pelvic_tilt",
"Col3" : "lumbar_lordosis_angle",
"Col4" : "sacral_slope",
"Col5" : "pelvic_radius",
"Col6" : "degree_spondylolisthesis",
"Col7" : "pelvic_slope",
"Col8" : "direct_tilt",
"Col9" : "thoracic_slope",
"Col10" :"cervical_tilt",
"Col11" : "sacrum_angle",
"Col12" : "scoliosis_slope",
"Class_att" : "class"}, inplace=True)
DataFrame.info() 打印关于数据帧的信息,包括index
dtype 和column
dtype、non-null
值和内存使用情况。我们可以使用info()
来知道一个数据集是否包含任何缺失值。
dataset.info()
可视化异常和正常情况的数量:
abnormal
病例的趋势比normal
病例高 2 倍。
dataset["class"].value_counts().sort_index().plot.bar()
class distribution
检查功能之间的相关性:
相关系数 是某种相关性的数值度量,表示两个变量之间的统计关系。
dataset.corr()
可视化与热图的关联:
plt.subplots(figsize=(12,8))
sns.heatmap(dataset.corr())
correlation between features
自定义相关图:
一个对图允许我们看到单个变量的分布和两个变量之间的关系。
sns.pairplot(dataset, hue="class")
在下图中,很多事情都在发生。让我们试着理解结对情节。在结对情节中,我们主要需要了解两件事。一个是特征的分布,另一个是一个特征与所有其他特征之间的关系。如果我们看对角线,我们可以看到每个特征的分布。让我们考虑一下first row X first column
,这条对角线向我们展示了pelvic_incidence
的分布。同样,如果我们观察second row X second column
对角线,我们可以看到pelvic_tilt
的分布。除对角线以外的所有单元格都显示了一个要素与另一个要素之间的关系。让我们考虑一下first row X second column
,这里我们可以说明pelvic_incidence
和pelvic_tilt
之间的关系。
custom correlogram
使用直方图可视化要素:
一个直方图 是显示频率分布最常用的图形。
dataset.hist(figsize=(15,12),bins = 20, color="#007959AA")
plt.title("Features Distribution")
plt.show()
features histogram
检测和移除异常值
plt.subplots(figsize=(15,6))
dataset.boxplot(patch_artist=True, sym=”k.”)
plt.xticks(rotation=90)
Detect outliers using boxplot
移除异常值:
# we use tukey method to remove outliers.
# whiskers are set at 1.5 times Interquartile Range (IQR)def remove_outlier(feature):
first_q = np.percentile(X[feature], 25)
third_q = np.percentile(X[feature], 75)
IQR = third_q - first_q
IQR *= 1.5 minimum = first_q - IQR # the acceptable minimum value
maximum = third_q + IQR # the acceptable maximum value
mean = X[feature].mean() """
# any value beyond the acceptance range are considered
as outliers. # we replace the outliers with the mean value of that
feature.
""" X.loc[X[feature] < minimum, feature] = mean
X.loc[X[feature] > maximum, feature] = mean # taking all the columns except the last one
# last column is the labelX = dataset.iloc[:, :-1]for i in range(len(X.columns)):
remove_outlier(X.columns[i])
移除异常值后:
features distribution after removing outliers
特征缩放:
特征缩放尽管标准化(或 Z 分数归一化)对于许多机器学习算法来说可能是一个重要的预处理步骤。我们的数据集包含在量级、单位和范围上差异很大的要素。但由于大多数机器学习算法在计算中使用两个数据点之间的欧几里德距离,这将产生一个问题。为了避免这种影响,我们需要将所有的特征放在相同的量级上。这可以通过特征缩放来实现。
scaler = MinMaxScaler()
scaled_data = scaler.fit_transform(X)
scaled_df = pd.DataFrame(data = scaled_data, columns = X.columns)
scaled_df.head()
dataset head after feature scaling
标签编码:
像 XGBoost 这样的算法只能将数值作为它们的预测变量。因此,我们需要编码我们的分类值。来自sklearn.preprocessing
包的 LabelEncoder 对值在0
和n_classes-1
之间的标签进行编码。
label = dataset["class"]encoder = LabelEncoder()
label = encoder.fit_transform(label)
模型培训和评估:
X = scaled_df
y = labelX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.15, random_state=0)clf_gnb = GaussianNB()
pred_gnb = clf_gnb.fit(X_train, y_train).predict(X_test)
accuracy_score(pred_gnb, y_test)# Out []: 0.8085106382978723clf_svc = SVC(kernel="linear")
pred_svc = clf_svc.fit(X_train, y_train).predict(X_test)
accuracy_score(pred_svc, y_test)# Out []: 0.7872340425531915clf_xgb = XGBClassifier()
pred_xgb = clf_xgb.fit(X_train, y_train).predict(X_test)
accuracy_score(pred_xgb, y_test)# Out []: 0.8297872340425532
功能重要性:
fig, ax = plt.subplots(figsize=(12, 6))
plot_importance(clf_xgb, ax=ax)
feature importance
边缘地块
边际图允许我们研究两个数字变量之间的关系。中间的图表显示了它们的相关性。
让我们想象一下degree_spondylolisthesis
和class
之间的关系:
sns.set(style="white", color_codes=True)
sns.jointplot(x=X["degree_spondylolisthesis"], y=label, kind='kde', color="skyblue")
Marginal plot between degree_spondylolisthesis and class
就这些。感谢阅读。😃
完整代码请访问 Kaggle 或 Google Colab 。
如果你喜欢这篇文章,然后给👏鼓掌。编码快乐!