<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="/rss.xsl" type="text/xsl"?>
<rss version="2.0">
  <channel>
    <title>IT瘾时间推荐</title>
    <link>https://itindex.net/categories/时间</link>
    <description>IT社区推荐资讯 - ITIndex.net</description>
    <language>zh</language>
    <copyright>https://itindex.net/</copyright>
    <generator>https://itindex.net/</generator>
    <docs>http://backend.userland.com/rss</docs>
    <image>
      <url>https://itindex.net/images/logo.gif</url>
      <title>IT社区推荐资讯 - ITIndex.net</title>
      <link>https://itindex.net/categories/时间</link>
    </image>
    <item>
      <title>这7个技巧，让大脑长时间保持巅峰状态</title>
      <link>https://itindex.net/detail/62929-%E6%8A%80%E5%B7%A7-%E5%A4%A7%E8%84%91-%E6%97%B6%E9%97%B4</link>
      <description>&lt;h6&gt;作者：李睿秋Lachel&lt;/h6&gt; &lt;h6&gt;来源：微信公众号：L先生说（ID：lxianshengmiao）&lt;/h6&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;经常看到不少人问一个问题：有什么办法，可以让自己保持长时间的专注呢？&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;其实，我是不提倡这么做的。因为大脑需要休息。长时间（好几个小时）保持高度专注，无论是工作，还是学习，都是一件不现实的事情，一定会导致疲劳。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;但是，在保证「张弛有度」的前提下，还是有一些技巧，可以让大脑保持清醒，运转得更高效的。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;如果你也遇到过下面这些问题：&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;下班后回到家，想好好学习一下，但却总是感到特别疲惫；&lt;/p&gt;  &lt;p&gt;总觉得大脑转不动，脑海里像生了锈一样，有一种凝涩感；&lt;/p&gt;  &lt;p&gt;有很多任务要处理，却总是心烦意乱，没办法集中注意力……&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;  &lt;br /&gt;那么，今天的文章，也许能帮上你一点忙。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://ossimg.xinli001.com/20240826/a9df63180fdc99fa1a73dbe891d496ee.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;1.  适当摄入一些蛋白质&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;许多人习惯在早餐吃碳水，比如粥、面包、馒头……因为碳水能够提供饱腹感，也能迅速供能，为上午的工作奠定基础。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;但如果你想提高大脑的运作能力，不妨试着在食谱里加入一些蛋白质。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;为什么呢？原因在于：  &lt;strong&gt;对大脑至关重要的两种神经递质，它们的原料都是氨基酸。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;一种是多巴胺。它能够为我们提供动力，也能帮助我们更好地进行决策。如果缺乏多巴胺，你会容易觉得无精打采，对什么都提不起兴趣，当然更没有办法好好工作。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;多巴胺的重要原料是酪氨酸。研究发现：在早餐加入更多的蛋白质，可以提高多巴胺的分泌，增强决策能力(Strang等, 2017)。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;另一种是血清素。它能够调控情绪，让我们感到更舒适、更放松、更愉悦。如果缺乏血清素，你会容易感到焦虑，情绪低落，没法集中注意力。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;血清素的原料是什么呢？是色氨酸。它是一种必需氨基酸，人体无法合成，只能从食物中摄取。因此，我们必须摄入足够的蛋白质，才能确保血清素的平衡。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;当然，不仅限于早餐，午餐、晚餐，乃至于两餐之间的零食，都可以通过摄入适当的蛋白质，来为我们的多巴胺和血清素提供足够的原料，让大脑拥有充分的动力。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;另外，也要注意三点：&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;1）摄入适当的蛋白质，不意味着只吃蛋白质，而是要  &lt;strong&gt;注意膳食均衡&lt;/strong&gt;。因为制造血清素的过程，除了色氨酸，还需要维生素、铁、锌和叶酸等，缺乏了这些营养素，血清素都是无法稳定制造的。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;2）  &lt;strong&gt;千万不能「不吃碳水」。&lt;/strong&gt;不吃碳水会带来许多问题，比如：碳水可以帮助消化掉血液中其他氨基酸，使得色氨酸更容易通过血脑屏障抵达大脑。如果缺乏碳水，色氨酸是难以被身体吸收的。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;3）甚至，蛋白质也不宜过量，  &lt;strong&gt;适当摄入即可。&lt;/strong&gt;因为蛋白质中的支链氨基酸会阻碍人体对色氨酸的吸收，如果摄入过多，同样会阻碍血清素的合成。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;简而言之：如果你日常饮食习惯以碳水为主，不妨加入一些蛋白质，尽量实现均衡。这样可以为大脑提供更充分的动力。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://ossimg.xinli001.com/20240826/99637fe6779f38b057831b2c63051789.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;2.  切换不同任务来「刷新」大脑&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;大脑有一件非常讨厌的事情：那就是长时间专注在一个任务上。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;原因在于：  &lt;strong&gt;大脑是需要新鲜刺激的。&lt;/strong&gt;长时间处理同一个任务，大脑就会感到疲劳，从而导致注意力涣散、昏昏欲睡、分心、走神……这不但会容易出错，更会严重影响我们的学习和记忆效率。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;许多人有这样的习惯：想在短时间内啃下一个领域，就带着书去图书馆、咖啡厅，一学就是一整天。但这种做法其实是低效的。它可能会带来烦躁感，会让你感到心烦意乱、看不进去眼前的文字，或是为了完成今天的任务而草草了事……&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;像这样一天下来，你真正吸收到的知识，可能只有一半不到。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;更好的做法是什么呢？是  &lt;strong&gt;为自己设定2-3个不同的领域，然后把今天的时间划分成多个不同段落&lt;/strong&gt;，按照1-2-3-1-2-3……这样交替进行，循环往复。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;每个段落的时间可长可短。比如领域1非常重要，而领域2和3不太重要，那么就可以为领域1分配更长的时间，2和3分配更短的时间。比如给前者分配1-2个小时，后者分配半小时左右。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;同样，在同一个领域之内，最好也交替进行不同的任务。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;举个例子：比如你要进行看书、做题和背诵，那么最好在同一天里面交替进行这三项任务，而不是今天全部用来看书、明天全部用来做题……&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;这样可以不断「刷新」大脑，让大脑始终工作在最佳的状态下。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://ossimg.xinli001.com/20240826/fea058a7f32d7e015c043ccf76cb2c82.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;3.  采取主动休息来恢复精力&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;许多人日常的模式是「被动休息」，亦即长时间埋头苦干，直到下班了或周末才一口气休息恢复。这其实是低效的，你相当于一直处于「电量不足」的状态中。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;更高效的做法是什么呢？是  &lt;strong&gt;变被动休息为「主动休息」。也就是交替着工作和休息，自己掌控休息的节奏。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;一个简单的建议是：把工作划分成多个小阶段，每个阶段大约20分钟到1小时。结束了一个阶段之后，就主动休息一下，换换脑子，做一些别的事情。比如看看有趣的视频，读几页书，写几行字，起来散散步、活动身体……&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;Draugiem 集团做过一项调查：他们调查了企业里工作效率最高的 10% 的员工，发现：他们在工作总时长上跟其他员工并无显著差别，那么差别最大的地方在哪里呢？他们平均每工作52分钟，就会休息17分钟。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;组织心理学家 John Trougakos 也认为：我们每小时至少要休息10-15分钟，休息的时候不要看邮件、思考工作，而是要做一些不同的事情。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;这一点可以跟上一条相结合。举个例子：假设你给领域1设定了比较长的时间，那么也许就可以分配给它3次休息；而领域2和领域3设置了较短的时间，那么就各分配1次休息……诸如此类。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;那么，休息的时候可以做些什么呢？&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;我的建议是：尽量脱离开工作的场景，做一些跟工作全然不同的事情。比如：挑一些关于历史、文化、美食、旅行……的纪录片，一点点看，慢慢看，补充一些稀奇古怪的知识，同时还能得到放松。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;又或者：回顾一下自己最近看过的小说、综艺和影视剧，简单地给它们打打分，用几句话写一下评价、心得，等等。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;这可以快速地帮助大脑「回血」，让大脑充满能量，更好地应对下一个阶段的任务。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://ossimg.xinli001.com/20240826/414f373c4024e1af78d0bd75246e1887.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;4.  少量多次的冲刺工作法&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;可能有朋友会问：可是我就喜欢专注，主动休息对我来说是一种打断，这该怎么办呢？&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;这里，其实就需要我们打破对「专注」的迷思。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;许多人说的专注，可能是一种心流状态，也就是让自己全身心投入在某项工作里面，注意力完全被占满，大脑高效运转的状态 —— 但是，这种状态是能够长时间持续的吗？其实很多时候是不能的。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;它更多的，并不是一个持续的、连续不断的「专注」，而是一个一个关卡接连不断，需要不停调动你的技能，让你动动脑筋、跳一跳才能过去 ——   &lt;strong&gt;这么一连串的「闯关」过程。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;因此，我的做法是：每当我开始一项工作时，我会把它  &lt;strong&gt;划分成多个小块，把每个小块设定成一个问题和挑战。&lt;/strong&gt;然后，要求自己去解决这个问题，完成这个挑战。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;以写文章为例，我可能会这样要求自己：让自己在一段时间里，不走神、不分心，集中精力地解决下面任意一个问题：&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;想出一个具体的场景和案例，来描述清楚这个现象；&lt;/p&gt;  &lt;p&gt;想出一个有趣的类比，来讲清楚这个概念；&lt;/p&gt;  &lt;p&gt;把这段逻辑提炼一下，用尽量简练的两三句话，概括这段复杂的逻辑；&lt;/p&gt;  &lt;p&gt;把这段话重新修改一下，让语气更流畅、更自然……&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;  &lt;br /&gt;解决了一个问题之后，如果你尚有余勇，那就一鼓作气继续解决下一个问题；如果觉得有些疲倦了，那就果断休息一下，不用担心退出「专注」 —— 因为你的节奏，始终是由自己所把握和控制的。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;就这样，一步一步，一项挑战接一项挑战逐步完成，一天的工作就这么完成了。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;实际上，这才是一种更高效的专注。我们要锻炼的，其实不是「长时间处于心流状态」的能力，而是  &lt;strong&gt;「随时随地都能进入心流状态」&lt;/strong&gt;的能力。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://ossimg.xinli001.com/20240826/bd7ce3885cbcd60b0766cdd452309695.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;5.  在白天接受更多的阳光&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;我们几乎每天都在室内，在人造灯光下度过。这就导致对我们来说，白天和夜晚几乎没什么区别。但这样其实是不利于大脑工作的。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;原因在于：  &lt;strong&gt;光照会影响大脑的节律。&lt;/strong&gt;在光照更强的环境下，大脑会进入高度警觉的状态，马力全开；反之，在光照较暗的环境下，大脑会开始分泌褪黑素，准备进入睡眠。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;换言之，大脑正是通过依据光照的强弱变化，来判断让自己进入什么状态。如果长时间处于室内和人造灯光下，大脑这种能力就会被削弱，从而使得节律混乱。表现在外就是，白天昏昏沉沉、哈欠连天；晚上则精神振奋、毫无睡意。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;因此，一个简单有效的方法就是：白天，当我们需要工作和学习时，尽可能处于自然光下，在能够晒到太阳的地方工作；而晚上则将光线调暗。  &lt;strong&gt;让大脑能够正确地区分白天和夜晚，培养正常的节律。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;有许多研究已经发现：在白天接受更多的自然光，可以极大地提升大脑的运转效率。比如：&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;2013年一项研究发现：暴露在阳光下一个小时，对反应速度的提升比两杯咖啡的效果更高(Beaven和Ekström，2013)。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;2020年一项研究发现：在日光充足的办公室工作的员工，平均睡眠时间增加37分钟，在一系列战略思维测试中成绩也高出42%(Boubekri等，2020)。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;与此同时，阳光还能通过促进合成维生素D和血清素，来调节我们的情绪，使大脑能够发挥出100%的状态，更好地投入工作。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;可能有人会问：人造灯光能替代阳光吗？很遗憾，我们日常在家里和办公室使用的灯光照度太低（可能只有阳光的数十分之一），是很难达到阳光的效果的。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;所以，  &lt;strong&gt;请在白天的时候多晒太阳，&lt;/strong&gt;最好从早上起床就开始。如果有条件，尽量在能晒到太阳的地方工作、学习；如果没有条件，也要在工作的间隙出去走走，在阳光下散步。当然，要做好防晒。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://ossimg.xinli001.com/20240826/3731c1ff2e441576c2b75689cc868471.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;6.  频繁活动来减少久坐的伤害&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;如果说什么事情，会对大脑造成危害而又常被我们忽略，那可能就是久坐了。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;原因有很多，比如：&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;久坐不利于心脑血管代谢，这会降低我们的认知能力；&lt;/p&gt;  &lt;p&gt;久坐会使得大脑中部分脑区缺氧，影响大脑的整体协作能力；&lt;/p&gt;  &lt;p&gt;久坐会导致身体产生微小但切实存在的疲倦，让头脑变得昏沉，难以转动……&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;  &lt;br /&gt;因此，许多人也许都有这种体验：工作、学习，又或者是追剧时，一坐就是几小时，不挪窝，全身心扑在电脑前，然后就感觉有点昏昏沉沉，大脑好像转动都不太灵活了。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;要牢记一点：我们的身体从来都不是设计用来「静止不动」的。  &lt;strong&gt;一连几个小时的久坐，并不是一种休息，而是一种负担。&lt;/strong&gt;最好的休息方式，从来都不是坐着不动，而是站、坐、躺和行走的来回切换。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;所以，一个最简单的做法，就是  &lt;strong&gt;频繁起来活动。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;研究发现：当我们活动的时候，大脑中的血管会扩张，从而使得血流量增大，更不容易发生堵塞。同时，大脑中的不同脑区会被激活，从而降低单一脑区长时间被激活而导致的堵塞风险。这些都可以有效减少久坐的后果。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;你可以把这一点跟第三点结合起来：当你主动休息时，不妨起来活动一下，让大脑放松。甚至，如果有机会，试一试原地跑跳、散步、快步走，让更多血液流经大脑，改善整体的代谢掌控。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;这是让大脑避免陷入困倦最简单、性价比最高的办法。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://ossimg.xinli001.com/20240826/4be3f18c2e39130e06f68ec0a3d8e06e.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;  &lt;strong&gt;7.  养成动脑休息的习惯&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;许多人有一个误区，总是认为动脑思考是需要消耗能量的，对大脑是一种负担，所以，休息时喜欢做不用动脑的事情，比如看不费脑子的综艺、影视剧，刷不费脑子的信息流和短视频，等等。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;这其实是完全错误的。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;实际上，大脑的耗能是恒定的，永远保持在20%左右。即使你不动脑，大脑也会在后台运行，将碎片信息整理起来、回放先前学到的内容，勤勤恳恳地进行自我整理和完善。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;反过来，如果你为了消磨时间，而特地去做一些不费脑子的事情，看似在让大脑放松，实际上这反而让大脑沐浴在信息流的冲击之下。既没有发挥它「前台」的功能，亦即用来思考问题、处理任务；也没有发挥它后台的作用，也就是整理和回放信息。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;也就是说，  &lt;strong&gt;大脑最忌讳的，就是既不让它工作，也不让它安静，而是不断用被动接受的信息冲击它。&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;   &lt;br /&gt;&lt;/strong&gt;这对大脑来说，既是一种浪费，也是一种耗损。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;因此，更好的做法是什么呢？是让大脑习惯「动脑」这件事。即使你工作做完了，没有需要处理的任务，也不妨转换一下脑子、切换一下思维，做一做别的事情，激活不同的脑区，从中得到愉悦感和放松。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;比如：&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;看一些知识性的视频，一边看一边记录自己的感想；&lt;/p&gt;  &lt;p&gt;读几篇自己感兴趣的文章，给大脑一些有趣的启发；&lt;/p&gt;  &lt;p&gt;看几页不同领域的书籍，让大脑获取新鲜的知识点；&lt;/p&gt;  &lt;p&gt;……&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;  &lt;br /&gt;如果实在什么都不想做，那不妨静静地待着，发呆、放空，让它在后台组织整理信息，也胜过用信息流去干扰它。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;主动掌控大脑，让它习惯和适应动脑的模式，这样，才能在需要的时候，更充分地调动它。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;这是近期关于大脑实用技巧的第四篇文章。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;前三篇文章可以看：&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;a href="http://mp.weixin.qq.com/s?__biz=MzAxNTY0NjEzNg==&amp;mid=2247488118&amp;idx=1&amp;sn=7e7d22334379979dfd33f840f66c6cc1&amp;chksm=9b81bea1acf637b7596685bbfbf0c0240d306fae84cc8feb69437e967fe66f8637396c46514c&amp;scene=21#wechat_redirect" target="_blank"&gt;每天半小时，让大脑变得更聪明&lt;/a&gt;  &lt;br /&gt;  &lt;a href="http://mp.weixin.qq.com/s?__biz=MzAxNTY0NjEzNg==&amp;mid=2247488162&amp;idx=1&amp;sn=5cafcc1ae5be4cec8f36f62c86158f0f&amp;chksm=9b81be75acf637635a83fe8c32f9717540c16b9aec704f9112aeb176b7fc0bcfdd91991ac28d&amp;scene=21#wechat_redirect" target="_blank"&gt;锻炼大脑，这里有一套全指南&lt;/a&gt;  &lt;br /&gt;  &lt;a href="http://mp.weixin.qq.com/s?__biz=MzAxNTY0NjEzNg==&amp;mid=2247488202&amp;idx=1&amp;sn=5848c5ed91fe23771c197f81ff61c36d&amp;chksm=9b81be1dacf6370b28b65e1d787032edf86e8c4aefc4ffc6afdc0ba8166c8963c933abdb6a25&amp;scene=21#wechat_redirect" target="_blank"&gt;激活大脑潜力的7个方法&lt;/a&gt;  &lt;br /&gt;  &lt;br /&gt;希望今天的内容，同样能够给你一些启发。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;h6&gt;作者简介：李睿秋Lachel，本文转载自微信公众号：L先生说（ID：lxianshengmiao），李睿秋Lachel，《打开心智》作者，专注心理学、认知科学和心智成长。每周四原创更新，与你一起探索事物本质。  &lt;br /&gt;&lt;/h6&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62929-%E6%8A%80%E5%B7%A7-%E5%A4%A7%E8%84%91-%E6%97%B6%E9%97%B4</guid>
      <pubDate>Mon, 26 Aug 2024 23:31:21 CST</pubDate>
    </item>
    <item>
      <title>[OpenAI] 本人在 AIGC 这个赛道待了几个月，对于国内这块市场创业方面的情况，今天有时间简单概括一下。</title>
      <link>https://itindex.net/detail/62830-openai-%E6%9C%AC%E4%BA%BA-aigc</link>
      <description>一开始二月份左右，首批赚钱的是靠卖 GPT 提问次数。起初，这个市场就是赚好奇心的钱，但现在情况是门槛降低了，到处都是套壳产品，GitHub 上有十几个开源项目可供使用，热度也降低了。 &lt;br /&gt; &lt;br /&gt;然后是卖教程和信息差，赚 FOMO （ fear of missing out ）焦虑的钱。随着时间的推移，这些教材内容被互相转载，现在全部免费了，能够割韭菜的机会已经差不多被利用完了。还有个原因是没什么新的功能可以拿出来讲解，大部分都是老生常谈。 &lt;br /&gt; &lt;br /&gt;接下来很多人选择卖账号代理、MJ 账号、plus 会员和 API 密钥，目前也是卷的情况比较多，门槛低，一个账号只能赚几块、十块。 &lt;br /&gt; &lt;br /&gt;此外，还有一些门槛高一些的创业方式。比如做知识库，类似于国外流行的 chatbase ，为客户提供定制机器人。光在本人的微信上，我知道国内就有十家左右公司团队在从事这方面的项目。不过，向量数据库技术需要不断调试和完善，我们拭目以待接下来的技术发展。 &lt;br /&gt; &lt;br /&gt;数字人这个领域是应用最火的地方，但也感觉还不够成熟，技术还是生硬。。。。 &lt;br /&gt; &lt;br /&gt;当然还有不少 AI 图像绘画上面的商业运用，国外市场几家相对成熟，国内也不少团队受到启发在抄袭。 &lt;br /&gt; &lt;br /&gt;除了视频和绘图之外，AI 音频也有不少可以商业化的地方。这几天我们团队就在和人聊 AI 陪聊，还有 AI 练习外语，以及可以让会员赚钱的 AI 内容平台这三块项目，商业前景也不小。 &lt;br /&gt; &lt;br /&gt;所以说，AI 这块适合我们小团队的商机其实很多， &lt;br /&gt;创业就是要不断尝试，累积经验， &lt;br /&gt;多花时间了解行业新动态， &lt;br /&gt;多和别人交流，创建新的概念， &lt;br /&gt;多发言扩大自己在圈子里的知名度。 &lt;br /&gt; &lt;br /&gt;这也是我们当初办这个公益社区的主要原因。大家可以多去社区论坛上看看好的干货，新的动态，创业概念， &lt;br /&gt;链接：  &lt;a href="https://www.aiahz.com/community" rel="nofollow noopener" target="_blank"&gt;https://www.aiahz.com/community&lt;/a&gt; &lt;br /&gt; &lt;br /&gt;另外，多在群里交流，因为一个人的能力有限，所以要懂得与人配合，寻找创业伙伴也很关键。 &lt;br /&gt; &lt;br /&gt;AI 这块趋势的爆发只会越来越大，特别是在各大公司都争斗的情况下，说它是日新月异也不过分。 &lt;br /&gt; &lt;br /&gt;问题是，机会来的时候，我们准备好了吗？
	&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62830-openai-%E6%9C%AC%E4%BA%BA-aigc</guid>
      <pubDate>Thu, 10 Aug 2023 00:52:53 CST</pubDate>
    </item>
    <item>
      <title>下篇 | 使用  Transformers 进行概率时间序列预测</title>
      <link>https://itindex.net/detail/62624-transformers-%E6%A6%82%E7%8E%87-%E6%97%B6%E9%97%B4%E5%BA%8F%E5%88%97</link>
      <description>&lt;p&gt;在《使用  Transformers 进行概率时间序列预测》的第一部分里，我们为大家介绍了传统时间序列预测和基于 Transformers 的方法，也一步步准备好了训练所需的数据集并定义了环境、模型、转换和   &lt;code&gt;InstanceSplitter&lt;/code&gt;。本篇内容将包含从数据加载器，到前向传播、训练、推理和展望未来发展等精彩内容。&lt;/p&gt; &lt;h2&gt;创建 PyTorch 数据加载器&lt;/h2&gt; &lt;p&gt;有了数据，下一步需要创建 PyTorch DataLoaders。它允许我们批量处理成对的 (输入, 输出) 数据，即 (  &lt;code&gt;past_values&lt;/code&gt; ,   &lt;code&gt;future_values&lt;/code&gt;)。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;from gluonts.itertools import Cyclic, IterableSlice, PseudoShuffled
from gluonts.torch.util import IterableDataset
from torch.utils.data import DataLoader

from typing import Iterable

def create_train_dataloader(
    config: PretrainedConfig,
    freq,
    data,
    batch_size: int,
    num_batches_per_epoch: int,
    shuffle_buffer_length: Optional[int] = None,
    **kwargs,
) -&amp;gt; Iterable:
    PREDICTION_INPUT_NAMES = [
        &amp;quot;static_categorical_features&amp;quot;,
        &amp;quot;static_real_features&amp;quot;,
        &amp;quot;past_time_features&amp;quot;,
        &amp;quot;past_values&amp;quot;,
        &amp;quot;past_observed_mask&amp;quot;,
        &amp;quot;future_time_features&amp;quot;,
        ]

    TRAINING_INPUT_NAMES = PREDICTION_INPUT_NAMES + [
        &amp;quot;future_values&amp;quot;,
        &amp;quot;future_observed_mask&amp;quot;,
        ]
    
    transformation = create_transformation(freq, config)
    transformed_data = transformation.apply(data, is_train=True)
    
    # we initialize a Training instance
    instance_splitter = create_instance_splitter(
        config, &amp;quot;train&amp;quot;
    ) + SelectFields(TRAINING_INPUT_NAMES)


    # the instance splitter will sample a window of 
    # context length + lags + prediction length (from the 366 possible transformed time series)
    # randomly from within the target time series and return an iterator.
    training_instances = instance_splitter.apply(
        Cyclic(transformed_data)
        if shuffle_buffer_length is None
        else PseudoShuffled(
            Cyclic(transformed_data), 
            shuffle_buffer_length=shuffle_buffer_length,
        )
    )

    # from the training instances iterator we now return a Dataloader which will 
    # continue to sample random windows for as long as it is called
    # to return batch_size of the appropriate tensors ready for training!
    return IterableSlice(
        iter(
            DataLoader(
                IterableDataset(training_instances),
                batch_size=batch_size,
                **kwargs,
            )
        ),
        num_batches_per_epoch,
    )&lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;  &lt;code&gt;def create_test_dataloader(
    config: PretrainedConfig,
    freq,
    data,
    batch_size: int,
    **kwargs,
):
    PREDICTION_INPUT_NAMES = [
        &amp;quot;static_categorical_features&amp;quot;,
        &amp;quot;static_real_features&amp;quot;,
        &amp;quot;past_time_features&amp;quot;,
        &amp;quot;past_values&amp;quot;,
        &amp;quot;past_observed_mask&amp;quot;,
        &amp;quot;future_time_features&amp;quot;,
        ]
    
    transformation = create_transformation(freq, config)
    transformed_data = transformation.apply(data, is_train=False)
    
    # we create a Test Instance splitter which will sample the very last 
    # context window seen during training only for the encoder.
    instance_splitter = create_instance_splitter(
        config, &amp;quot;test&amp;quot;
    ) + SelectFields(PREDICTION_INPUT_NAMES)
    
    # we apply the transformations in test mode
    testing_instances = instance_splitter.apply(transformed_data, is_train=False)
    
    # This returns a Dataloader which will go over the dataset once.
    return DataLoader(IterableDataset(testing_instances), batch_size=batch_size, **kwargs)&lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;  &lt;code&gt;train_dataloader = create_train_dataloader(
    config=config, 
    freq=freq, 
    data=train_dataset, 
    batch_size=256, 
    num_batches_per_epoch=100,
)

test_dataloader = create_test_dataloader(
    config=config, 
    freq=freq, 
    data=test_dataset,
    batch_size=64,
)&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;让我们检查第一批:&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;batch = next(iter(train_dataloader))
for k,v in batch.items():
  print(k,v.shape, v.type())

&amp;gt;&amp;gt;&amp;gt; static_categorical_features torch.Size([256, 1]) torch.LongTensor
    static_real_features torch.Size([256, 1]) torch.FloatTensor
    past_time_features torch.Size([256, 181, 2]) torch.FloatTensor
    past_values torch.Size([256, 181]) torch.FloatTensor
    past_observed_mask torch.Size([256, 181]) torch.FloatTensor
    future_time_features torch.Size([256, 24, 2]) torch.FloatTensor
    future_values torch.Size([256, 24]) torch.FloatTensor
    future_observed_mask torch.Size([256, 24]) torch.FloatTensor&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;可以看出，我们没有将   &lt;code&gt;input_ids&lt;/code&gt; 和   &lt;code&gt;attention_mask&lt;/code&gt; 提供给编码器 (训练 NLP 模型时也是这种情况)，而是提供   &lt;code&gt;past_values&lt;/code&gt;，以及   &lt;code&gt;past_observed_mask&lt;/code&gt;、  &lt;code&gt;past_time_features&lt;/code&gt;、  &lt;code&gt;static_categorical_features&lt;/code&gt; 和   &lt;code&gt;static_real_features&lt;/code&gt; 几项数据。&lt;/p&gt; &lt;p&gt;解码器的输入包括   &lt;code&gt;future_values&lt;/code&gt;、  &lt;code&gt;future_observed_mask&lt;/code&gt; 和   &lt;code&gt;future_time_features&lt;/code&gt;。   &lt;code&gt;future_values&lt;/code&gt; 可以看作等同于 NLP 训练中的   &lt;code&gt;decoder_input_ids&lt;/code&gt;。&lt;/p&gt; &lt;p&gt;我们可以参考   &lt;a href="https://huggingface.co/docs/transformers/model_doc/time_series_transformer#transformers.TimeSeriesTransformerForPrediction.forward.past_values" rel="nofollow noreferrer"&gt;Time Series Transformer 文档&lt;/a&gt; 以获得对它们中每一个的详细解释。&lt;/p&gt; &lt;h2&gt;前向传播&lt;/h2&gt; &lt;p&gt;让我们对刚刚创建的批次执行一次前向传播:&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;# perform forward pass
outputs = model(
    past_values=batch[&amp;quot;past_values&amp;quot;],
    past_time_features=batch[&amp;quot;past_time_features&amp;quot;],
    past_observed_mask=batch[&amp;quot;past_observed_mask&amp;quot;],
    static_categorical_features=batch[&amp;quot;static_categorical_features&amp;quot;],
    static_real_features=batch[&amp;quot;static_real_features&amp;quot;],
    future_values=batch[&amp;quot;future_values&amp;quot;],
    future_time_features=batch[&amp;quot;future_time_features&amp;quot;],
    future_observed_mask=batch[&amp;quot;future_observed_mask&amp;quot;],
    output_hidden_states=True
)&lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;  &lt;code&gt;print(&amp;quot;Loss:&amp;quot;, outputs.loss.item())

&amp;gt;&amp;gt;&amp;gt; Loss: 9.141253471374512&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;目前，该模型返回了损失值。这是由于解码器会自动将   &lt;code&gt;future_values&lt;/code&gt; 向右移动一个位置以获得标签。这允许计算预测结果和标签值之间的误差。&lt;/p&gt; &lt;p&gt;另请注意，解码器使用 Causal Mask 来避免预测未来，因为它需要预测的值在   &lt;code&gt;future_values&lt;/code&gt; 张量中。&lt;/p&gt; &lt;h2&gt;训练模型&lt;/h2&gt; &lt;p&gt;是时候训练模型了！我们将使用标准的 PyTorch 训练循环。&lt;/p&gt; &lt;p&gt;这里我们用到了    &lt;a href="https://huggingface.co/docs/accelerate/index" rel="nofollow noreferrer"&gt;Accelerate&lt;/a&gt; 库，它会自动将模型、优化器和数据加载器放置在适当的   &lt;code&gt;device&lt;/code&gt; 上。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;from accelerate import Accelerator
from torch.optim import Adam

accelerator = Accelerator()
device = accelerator.device

model.to(device)
optimizer = Adam(model.parameters(), lr=1e-3)
 
model, optimizer, train_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, 
)

for epoch in range(40):
    model.train()
    for batch in train_dataloader:
        optimizer.zero_grad()
        outputs = model(
            static_categorical_features=batch[&amp;quot;static_categorical_features&amp;quot;].to(device),
            static_real_features=batch[&amp;quot;static_real_features&amp;quot;].to(device),
            past_time_features=batch[&amp;quot;past_time_features&amp;quot;].to(device),
            past_values=batch[&amp;quot;past_values&amp;quot;].to(device),
            future_time_features=batch[&amp;quot;future_time_features&amp;quot;].to(device),
            future_values=batch[&amp;quot;future_values&amp;quot;].to(device),
            past_observed_mask=batch[&amp;quot;past_observed_mask&amp;quot;].to(device),
            future_observed_mask=batch[&amp;quot;future_observed_mask&amp;quot;].to(device),
        )
        loss = outputs.loss

        # Backpropagation
        accelerator.backward(loss)
        optimizer.step()

        print(loss.item())&lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;推理&lt;/h2&gt; &lt;p&gt;在推理时，建议使用   &lt;code&gt;generate()&lt;/code&gt; 方法进行自回归生成，类似于 NLP 模型。&lt;/p&gt; &lt;p&gt;预测的过程会从测试实例采样器中获得数据。采样器会将数据集的每个时间序列的最后   &lt;code&gt;context_length&lt;/code&gt; 那么长时间的数据采样出来，然后输入模型。请注意，这里需要把提前已知的   &lt;code&gt;future_time_features&lt;/code&gt; 传递给解码器。&lt;/p&gt; &lt;p&gt;该模型将从预测分布中自回归采样一定数量的值，并将它们传回解码器最终得到预测输出:&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;model.eval()

forecasts = []

for batch in test_dataloader:
    outputs = model.generate(
        static_categorical_features=batch[&amp;quot;static_categorical_features&amp;quot;].to(device),
        static_real_features=batch[&amp;quot;static_real_features&amp;quot;].to(device),
        past_time_features=batch[&amp;quot;past_time_features&amp;quot;].to(device),
        past_values=batch[&amp;quot;past_values&amp;quot;].to(device),
        future_time_features=batch[&amp;quot;future_time_features&amp;quot;].to(device),
        past_observed_mask=batch[&amp;quot;past_observed_mask&amp;quot;].to(device),
    )
    forecasts.append(outputs.sequences.cpu().numpy())&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;该模型输出一个表示结构的张量 (  &lt;code&gt;batch_size&lt;/code&gt;,   &lt;code&gt;number of samples&lt;/code&gt;,   &lt;code&gt;prediction length&lt;/code&gt;)。&lt;/p&gt; &lt;p&gt;下面的输出说明: 对于大小为 64 的批次中的每个示例，我们将获得接下来 24 个月内的 100 个可能的值:&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;forecasts[0].shape

&amp;gt;&amp;gt;&amp;gt; (64, 100, 24)&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我们将垂直堆叠它们，以获得测试数据集中所有时间序列的预测:&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;forecasts = np.vstack(forecasts)
print(forecasts.shape)

&amp;gt;&amp;gt;&amp;gt; (366, 100, 24)&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我们可以根据测试集中存在的样本值，根据真实情况评估生成的预测。这里我们使用数据集中的每个时间序列的   &lt;a href="https://huggingface.co/spaces/evaluate-metric/mase" rel="nofollow noreferrer"&gt;MASE&lt;/a&gt; 和   &lt;a href="https://hf.co/spaces/evaluate-metric/smape" rel="nofollow noreferrer"&gt;sMAPE&lt;/a&gt; 指标 (metrics) 来评估:&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;from evaluate import load
from gluonts.time_feature import get_seasonality

mase_metric = load(&amp;quot;evaluate-metric/mase&amp;quot;)
smape_metric = load(&amp;quot;evaluate-metric/smape&amp;quot;)

forecast_median = np.median(forecasts, 1)

mase_metrics = []
smape_metrics = []
for item_id, ts in enumerate(test_dataset):
    training_data = ts[&amp;quot;target&amp;quot;][:-prediction_length]
    ground_truth = ts[&amp;quot;target&amp;quot;][-prediction_length:]
    mase = mase_metric.compute(
        predictions=forecast_median[item_id], 
        references=np.array(ground_truth), 
        training=np.array(training_data), 
        periodicity=get_seasonality(freq))
    mase_metrics.append(mase[&amp;quot;mase&amp;quot;])
    
    smape = smape_metric.compute(
        predictions=forecast_median[item_id], 
        references=np.array(ground_truth), 
    )
    smape_metrics.append(smape[&amp;quot;smape&amp;quot;])&lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;  &lt;code&gt;print(f&amp;quot;MASE: {np.mean(mase_metrics)}&amp;quot;)

&amp;gt;&amp;gt;&amp;gt; MASE: 1.361636922541396

print(f&amp;quot;sMAPE: {np.mean(smape_metrics)}&amp;quot;)

&amp;gt;&amp;gt;&amp;gt; sMAPE: 0.17457818831512306&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我们还可以单独绘制数据集中每个时间序列的结果指标，并观察到其中少数时间序列对最终测试指标的影响很大:&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;plt.scatter(mase_metrics, smape_metrics, alpha=0.3)
plt.xlabel(&amp;quot;MASE&amp;quot;)
plt.ylabel(&amp;quot;sMAPE&amp;quot;)
plt.show()&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000043457848" title=""&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;为了根据基本事实测试数据绘制任何时间序列的预测，我们定义了以下辅助绘图函数:&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;import matplotlib.dates as mdates

def plot(ts_index):
    fig, ax = plt.subplots()

    index = pd.period_range(
        start=test_dataset[ts_index][FieldName.START],
        periods=len(test_dataset[ts_index][FieldName.TARGET]),
        freq=freq,
    ).to_timestamp()

    # Major ticks every half year, minor ticks every month,
    ax.xaxis.set_major_locator(mdates.MonthLocator(bymonth=(1, 7)))
    ax.xaxis.set_minor_locator(mdates.MonthLocator())

    ax.plot(
        index[-2*prediction_length:], 
        test_dataset[ts_index][&amp;quot;target&amp;quot;][-2*prediction_length:],
        label=&amp;quot;actual&amp;quot;,
    )

    plt.plot(
        index[-prediction_length:], 
        np.median(forecasts[ts_index], axis=0),
        label=&amp;quot;median&amp;quot;,
    )
    
    plt.fill_between(
        index[-prediction_length:],
        forecasts[ts_index].mean(0) - forecasts[ts_index].std(axis=0), 
        forecasts[ts_index].mean(0) + forecasts[ts_index].std(axis=0), 
        alpha=0.3, 
        interpolate=True,
        label=&amp;quot;+/- 1-std&amp;quot;,
    )
    plt.legend()
    plt.show()&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;例如:&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;plot(334)&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000043457849" title=""&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;我们如何与其他模型进行比较？   &lt;a href="https://forecastingdata.org/#results" rel="nofollow noreferrer"&gt;Monash Time Series Repository&lt;/a&gt; 有一个测试集 MASE 指标的比较表。我们可以将自己的结果添加到其中作比较:&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;th align="center"&gt;Dataset&lt;/th&gt;   &lt;th align="center"&gt;SES&lt;/th&gt;   &lt;th align="center"&gt;Theta&lt;/th&gt;   &lt;th align="center"&gt;TBATS&lt;/th&gt;   &lt;th align="center"&gt;ETS&lt;/th&gt;   &lt;th align="center"&gt;(DHR-)ARIMA&lt;/th&gt;   &lt;th align="center"&gt;PR&lt;/th&gt;   &lt;th align="center"&gt;CatBoost&lt;/th&gt;   &lt;th align="center"&gt;FFNN&lt;/th&gt;   &lt;th align="center"&gt;DeepAR&lt;/th&gt;   &lt;th align="center"&gt;N-BEATS&lt;/th&gt;   &lt;th align="center"&gt;WaveNet&lt;/th&gt;   &lt;th align="center"&gt;    &lt;strong&gt;Transformer&lt;/strong&gt; (Our)&lt;/th&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td align="center"&gt;Tourism Monthly&lt;/td&gt;   &lt;td align="center"&gt;3.306&lt;/td&gt;   &lt;td align="center"&gt;1.649&lt;/td&gt;   &lt;td align="center"&gt;1.751&lt;/td&gt;   &lt;td align="center"&gt;1.526&lt;/td&gt;   &lt;td align="center"&gt;1.589&lt;/td&gt;   &lt;td align="center"&gt;1.678&lt;/td&gt;   &lt;td align="center"&gt;1.699&lt;/td&gt;   &lt;td align="center"&gt;1.582&lt;/td&gt;   &lt;td align="center"&gt;1.409&lt;/td&gt;   &lt;td align="center"&gt;1.574&lt;/td&gt;   &lt;td align="center"&gt;1.482&lt;/td&gt;   &lt;td align="center"&gt;    &lt;strong&gt;1.361&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;请注意，我们的模型击败了所有已知的其他模型 (另请参见相应   &lt;a href="https://openreview.net/pdf?id=wEc1mgAjU-" rel="nofollow noreferrer"&gt;论文&lt;/a&gt; 中的表 2) ，并且我们没有做任何超参数优化。我们仅仅花了 40 个完整训练调参周期来训练 Transformer。&lt;/p&gt; &lt;p&gt;当然，我们应该谦虚。从历史发展的角度来看，现在认为神经网络解决时间序列预测问题是正途，就好比当年的论文得出了   &lt;a href="https://www.sciencedirect.com/science/article/pii/S0169207021001679" rel="nofollow noreferrer"&gt;“你需要的就是 XGBoost”&lt;/a&gt; 的结论。我们只是很好奇，想看看神经网络能带我们走多远，以及 Transformer 是否会在这个领域发挥作用。这个特定的数据集似乎表明它绝对值得探索。&lt;/p&gt; &lt;h2&gt;下一步&lt;/h2&gt; &lt;p&gt;我们鼓励读者尝试我们的   &lt;a href="https://colab.research.google.com/github/huggingface/notebooks/blob/main/examples/time-series-transformers.ipynb" rel="nofollow noreferrer"&gt;Jupyter Notebook&lt;/a&gt; 和来自   &lt;a href="https://huggingface.co/datasets/monash_tsf" rel="nofollow noreferrer"&gt;Hugging Face Hub&lt;/a&gt; 的其他时间序列数据集，并替换适当的频率和预测长度参数。对于您的数据集，需要将它们转换为 GluonTS 的惯用格式，在他们的   &lt;a href="https://ts.gluon.ai/stable/tutorials/forecasting/extended_tutorial.html#What-is-in-a-dataset?" rel="nofollow noreferrer"&gt;文档&lt;/a&gt; 里有非常清晰的说明。我们还准备了一个示例   &lt;a href="https://github.com/huggingface/notebooks/blob/main/examples/time_series_datasets.ipynb" rel="nofollow noreferrer"&gt;Notebook&lt;/a&gt;，向您展示如何将数据集转换为  Hugging Face 数据集格式。&lt;/p&gt; &lt;p&gt;正如时间序列研究人员所知，人们对“将基于 Transformer 的模型应用于时间序列”问题很感兴趣。传统 vanilla Transformer 只是众多基于注意力 (Attention) 的模型之一，因此需要向库中补充更多模型。&lt;/p&gt; &lt;p&gt;目前没有什么能妨碍我们继续探索对多变量时间序列 (multivariate time series) 进行建模，但是为此需要使用多变量分布头 (multivariate distribution head) 来实例化模型。目前已经支持了对角独立分布 (diagonal independent distributions)，后续会增加其他多元分布支持。请继续关注未来的博客文章以及其中的教程。&lt;/p&gt; &lt;p&gt;路线图上的另一件事是时间序列分类。这需要将带有分类头的时间序列模型添加到库中，例如用于异常检测这类任务。&lt;/p&gt; &lt;p&gt;当前的模型会假设日期时间和时间序列值都存在，但在现实中这可能不能完全满足。例如   &lt;a href="https://woods-benchmarks.github.io/" rel="nofollow noreferrer"&gt;WOODS&lt;/a&gt; 给出的神经科学数据集。因此，我们还需要对当前模型进行泛化，使某些输入在整个流水线中可选。&lt;/p&gt; &lt;p&gt;最后，NLP/CV 领域从  &lt;a href="https://arxiv.org/abs/1810.04805" rel="nofollow noreferrer"&gt;大型预训练模型&lt;/a&gt; 中获益匪浅，但据我们所知，时间序列领域并非如此。基于 Transformer 的模型似乎是这一研究方向的必然之选，我们迫不及待地想看看研究人员和从业者会发现哪些突破！&lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;blockquote&gt;  &lt;p&gt;英文原文:    &lt;a href="https://huggingface.co/blog/time-series-transformers" rel="nofollow noreferrer"&gt;Probabilistic Time Series Forecasting with  Transformers&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;译者、排版: zhongdongy (阿东)&lt;/p&gt;&lt;/blockquote&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>人工智能 transformer huggingface</category>
      <guid isPermaLink="true">https://itindex.net/detail/62624-transformers-%E6%A6%82%E7%8E%87-%E6%97%B6%E9%97%B4%E5%BA%8F%E5%88%97</guid>
      <pubDate>Wed, 22 Feb 2023 23:16:11 CST</pubDate>
    </item>
    <item>
      <title>前端首屏渲染时间的极致优化</title>
      <link>https://itindex.net/detail/62461-%E5%89%8D%E7%AB%AF-%E6%B8%B2%E6%9F%93-%E6%97%B6%E9%97%B4</link>
      <description>&lt;p&gt;  &lt;img alt="20220722174820" src="https://segmentfault.com/img/bVc23MP" title="20220722174820"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;我们知道，用户体验是 Web 产品最为重要的部分。尽可能减少首屏加载时间，更为流畅地展示用户所需求的内容，会是用户是否留存的关键因素。&lt;/p&gt; &lt;p&gt;而随着现代 Web 业务可供用户的交互行为越来越多，前端项目的复杂度越来越高，每个页面的渲染时间也必然越来越长，这就导致了用户的体验不佳，用户的操作变慢。&lt;/p&gt; &lt;p&gt;为此，前端工程师们在首屏请求的各个阶段中持续钻研，不断探究如何将首次页面渲染的时间减少到更小，力求提供更为优秀的产品体验。&lt;/p&gt; &lt;h2&gt;CSR（Client Side Render）&lt;/h2&gt; &lt;p&gt;  &lt;img alt="20220720162452" src="https://segmentfault.com/img/bVc23MS" title="20220720162452"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;浏览器渲染是最简单，最符合 Web 应用设计思路的渲染方式。&lt;/p&gt; &lt;p&gt;所谓浏览器渲染，就是将应用所需的页面展示、前端逻辑、接口请求全都在用户的浏览器中执行。它很好的实现了前后端的解耦，让前端开发更为独立，也让后台实现更为简单。&lt;/p&gt; &lt;p&gt;同时，为了缓解用户的等待焦虑，我们可以用 loading 态，或者骨架屏，进一步提升异步请求接口时的用户体验。&lt;/p&gt; &lt;p&gt;不过，随着业务复杂程度提高，浏览器渲染的开销也会变大，我们无法控制用户侧使用的机器性能，很多时候，用户使用的机器性能甚至不足以满足应用的需求，造成卡顿，甚至崩溃，这一点在移动端上尤甚。&lt;/p&gt; &lt;p&gt;而浏览器渲染由于前端的动态性过高，也会带来 SEO 不佳的问题。&lt;/p&gt; &lt;h2&gt;SSR（Server Side Render）&lt;/h2&gt; &lt;p&gt;  &lt;img alt="20220720162513" src="https://segmentfault.com/img/bVc23MT" title="20220720162513"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;服务端渲染的出现时间实际上是要比浏览器渲染要更早的。在 Web 应用发展的早期，所有的 ASP、JSP 等模板引擎构建的前端页面实际上就是服务端渲染的结果。而此时的服务端渲染无法进行前后端职责的解耦，因此逐步被浏览器渲染淘汰。&lt;/p&gt; &lt;p&gt;但在处理首屏体验的问题上，服务端渲染有着独到的优势。它能提前再服务端中完成页面模板的数据填充，从而一次性返回完整的首屏内容，从而面对 SEO 的爬取时能获取到更多有效的关键信息。&lt;/p&gt; &lt;p&gt;此外，由于其能快速直出首页的真实数据，体验往往比 loading 态更佳，在 TTI 的表现上更为出色。&lt;/p&gt; &lt;p&gt;但是，服务端渲染也有其自身的局限性。因为从本质上来说，SSR 服务无法完全与前端页面解耦开来。因此市面上较完备的 SSR 解决方案都只解决首屏的服务端渲染，并采用同构的方式，增加一层 node 中间层的方式来解决前端与 SSR 服务的更新同步问题，并与后端开发项目解耦。&lt;/p&gt; &lt;p&gt;但这无疑增加了项目的复杂度，并且随着业务的复杂程度变高，服务端渲染往往需要调起多个接口去请求数据并填充页面，这样可能会导致在 TTFB 上有一定劣势。&lt;/p&gt; &lt;p&gt;当然，最重要的是，服务端渲染对于服务器的负载要求是很高的。&lt;/p&gt; &lt;p&gt;  &lt;img alt="20220722153734" src="https://segmentfault.com/img/bVc23MU" title="20220722153734"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;上图是引用的字节的某项目的 SSR 服务的单机 QPS 承载表现。我们可以看出，对于一个高访问量的网页应用来说，提供一个较为复杂的 SSR 服务的成本是相当高的，需要花费大量的金钱来堆机器。&lt;/p&gt; &lt;p&gt;因此，从降本增效的角度考虑，我们需要评估 SSR 带来的 ROI 是否符合预期。&lt;/p&gt; &lt;h2&gt;NSR（Native Side Render）&lt;/h2&gt; &lt;p&gt;在移动互联网的浪潮下，移动端机能飞速提升，那么 Web 应用是否能搭上这一班车，将 Native 的性能利用起来，提升页面渲染性能呢？答案是肯定的，这就需要介绍到 NSR 了。&lt;/p&gt; &lt;p&gt;  &lt;img alt="20220720162547" src="https://segmentfault.com/img/bVc23MV" title="20220720162547"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;Native 渲染的本质其实还是 SSR，只不过提供服务的 Server 转变为了客户端。由于需要用到客户端机能，因此此种实现通常应用在移动端 APP，或者 PWA 下。&lt;/p&gt; &lt;p&gt;当链接被点击时，先借助浏览器启用一个 JS 运行时，并加载 APP 中存储的 Html 模板，发送 xhr 请求预加载页面数据，从而在客户端本地拼接并渲染生成一个有数据的 Html 首屏，形成首次 NSR。同时可以将该首屏 Html 缓存在客户端，供下次页面打开时，实现   &lt;code&gt;stale-while-revalidate&lt;/code&gt; 的缓存效果。&lt;/p&gt; &lt;p&gt;由于 NSR 将服务器的渲染工作放在了客户端的一个个独立设备中，既实现了页面的预加载，同时又不会增加额外的服务器压力。达到秒看的效果。&lt;/p&gt; &lt;p&gt;这种能力在拥有客户端或者支持 PWA 的应用中应用广泛，例如手 Q，腾讯文档 APP 中都拥有通过 APP 中的离线包来实现首屏渲染加速的能力。&lt;/p&gt; &lt;h2&gt;ESR（Edge Side Render）&lt;/h2&gt; &lt;p&gt;那么，对于纯 Web 应用，而又由于兼容性等原因暂时无法支持 PWA 的页面，有没有一个合适的首屏渲染加速方案呢？&lt;/p&gt; &lt;p&gt;随着云与边缘计算的快速发展，前端页面也需要考虑分布式的请求处理优化。&lt;/p&gt; &lt;p&gt;  &lt;img alt="20220720162606" src="https://segmentfault.com/img/bVc23MW" title="20220720162606"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;我们知道，CDN 节点相比真实服务节点更贴近用户，能更快将内容返回。因此我们可以将静态的 Html 内容模板缓存在 CDN 上。当接到请求时，先快速将静态模板页面返回给用户，同时在 CDN 服务器上对页面动态部分发起向后端发起请求，并将获取到的动态内容在以流式下发的方式继续返回给用户。&lt;/p&gt; &lt;p&gt;这里实际上利用到了 HTTP 的 SSE（Server Send Events）协议，通过服务器向客户端发送单向事件流，实现同一个 Html 文件的分块传输预渲染。&lt;/p&gt; &lt;h2&gt;最佳实践是？&lt;/h2&gt; &lt;p&gt;这也是我们最近在腾讯文档中探索实践并落地的，通过服务中间节点的流式下发能力，实现多级首屏渲染加速。&lt;/p&gt; &lt;p&gt;对于一个复杂前端页面来说，首屏需要加载和运算的资源类型可能有很多，有需要客户端解析并执行的 JS 动效，也有需要服务端获取数据直出的数据分片展示页面。&lt;/p&gt; &lt;p&gt;通常来说，客户端只能等待服务端获取分片数据，并生成经过 SSR 渲染后的 HTML，才能开始进行 script 解析与 js 资源拉取的行为，最终渲染出完整的页面数据以及动效。&lt;/p&gt; &lt;p&gt;而既然他们所需要的计算方式不同，那么为什么不能并行来做呢？&lt;/p&gt; &lt;p&gt;我们可以在版本发布前，将未经过服务端直出的模板 HTML 进行解析，将需要发起资源请求的所有的外链脚本 url 提取出来，生成一个 HTML Header 结构，并将该 Header 内容伪装为正常 HTML 缓存在 CDN 节点中。&lt;/p&gt; &lt;p&gt;结合之前我们介绍的 HTTP SSE 协议，当用户请求时，我们可以以最快的速度向用户返回 CDN 中的 HTML header，从而让用户的浏览器提前拉取并解析外链资源。于此同时，CDN 节点将用户的请求转发给真实的服务端，从而让服务端进行真实数据的获取拼接并返回给客户端。&lt;/p&gt; &lt;p&gt;由于客户端此时已经提前拉取了外链资源，因此收到服务端分片的 SSR 后，客户端可以直接将真实数据渲染到页面中，而不需要再次等待外链资源的解析。&lt;/p&gt; &lt;p&gt;由于并行的关系，这样的 SSR 与 NSR 混合方式能大大降低复杂页面首屏渲染的时间，提升用户体验。&lt;/p&gt; &lt;p&gt;以百度首页的请求为例，通过 Chorme Network 提供的瀑布图，通过我们可以直观的看到一条请求的执行过程。&lt;/p&gt; &lt;p&gt;  &lt;img alt="20220913172530" src="https://segmentfault.com/img/bVc23MX" title="20220913172530"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;我们可以看出，除了 DNS 寻址与 SSL 建连是我们无法控制的以外，占用请求时间的大头是   &lt;code&gt;Waiting for server response&lt;/code&gt;，请求服务器 (CDN) 的时间，以及   &lt;code&gt;Content Download&lt;/code&gt;，外链资源的拉取时间。&lt;/p&gt; &lt;p&gt;而使用本文的混合方案后，理论上可以使总请求时间降低到 Max(A, B), (A 为   &lt;code&gt;Waiting for server response&lt;/code&gt;，B 为   &lt;code&gt;Content Download&lt;/code&gt;) 的水平。（当然，实际操作过程中，由于 CDN 节点进行了一次请求转发，因此拥有 SSR 能力的页面请求返回时间会更长一些）。&lt;/p&gt; &lt;h2&gt;结语&lt;/h2&gt; &lt;p&gt;前端的页面首屏时间优化是永恒的话题，本文介绍了前端界对首屏时间优化的进程，并提供了一种 SSR 与 NSR 混合的新思路，通过并行处理耗时任务的方式，进一步提升首屏加载时间，希望能够给大家提供一点参考价值。&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>javascript</category>
      <guid isPermaLink="true">https://itindex.net/detail/62461-%E5%89%8D%E7%AB%AF-%E6%B8%B2%E6%9F%93-%E6%97%B6%E9%97%B4</guid>
      <pubDate>Tue, 18 Oct 2022 11:19:21 CST</pubDate>
    </item>
    <item>
      <title>使用MyBatis拦截器后，摸鱼时间又长了。</title>
      <link>https://itindex.net/detail/62101-mybatis-%E6%97%B6%E9%97%B4</link>
      <description>&lt;hr&gt;&lt;/hr&gt;
 &lt;h2&gt;theme: cyanosis&lt;/h2&gt;
 &lt;p&gt;这是我参与2022首次更文挑战的第7天，活动详情查看：  &lt;a href="https://juejin.cn/post/7052884569032392740"&gt;2022首次更文挑战&lt;/a&gt;&lt;/p&gt;
 &lt;h2&gt;场景&lt;/h2&gt;
 &lt;p&gt;在后端服务开发时，现在很流行的框架组合就是SSM（SpringBoot + Spring + MyBatis），在我们进行一些业务系统开发时，会有很多的业务数据表，而表中的信息从新插入开始，整个生命周期过程中可能会进行很多次的操作。&lt;/p&gt;
 &lt;p&gt;比如，我们在某网站购买一件商品，会生成一条订单记录，在支付完金额后订单状态会变为已支付，等最后我们收到订单商品，这个订单状态会变成已完成等。&lt;/p&gt;
 &lt;p&gt;假设我们的订单表  &lt;code&gt;t_order&lt;/code&gt;结果如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8eb6ea0cf2bd41698cc45c0abe6b45ab~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;当订单创建时，需要设置  &lt;code&gt;insert_by&lt;/code&gt;，  &lt;code&gt;insert_time&lt;/code&gt;，  &lt;code&gt;update_by&lt;/code&gt;，  &lt;code&gt;update_time&lt;/code&gt;的值；&lt;/p&gt;
 &lt;p&gt;在进行订单状态更新时，则只需要更新  &lt;code&gt;update_by&lt;/code&gt;，  &lt;code&gt;update_time&lt;/code&gt;的值。&lt;/p&gt;
 &lt;p&gt;那应该如何处理呢？&lt;/p&gt;
 &lt;h2&gt;麻瓜做法&lt;/h2&gt;
 &lt;p&gt;最简单的做法，也是最容易想到的做法，就是在每个业务处理的代码中，对相关的字段进行处理。&lt;/p&gt;
 &lt;p&gt;比如订单创建的方法中，如下处理：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public void create(Order order){
    // ...其他代码
    // 设置审计字段
    Date now = new Date();
    order.setInsertBy(appContext.getUser());
    order.setUpdateBy(appContext.getUser());
    order.setInsertTime(now);
    order.setUpdateTime(now);
    orderDao.insert(order);
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;订单更新方法则只设置  &lt;code&gt;updateBy&lt;/code&gt;和  &lt;code&gt;updateTime&lt;/code&gt;：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public void update(Order order){
    // ...其他代码

    // 设置审计字段
    Date now = new Date();
    order.setUpdateBy(appContext.getUser());
    order.setUpdateTime(now);
    orderDao.insert(order);
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;这种方式虽然可以完成功能，但是存在一些问题：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;需要在每个方法中按照不同的业务逻辑决定设置哪些字段；&lt;/li&gt;
  &lt;li&gt;在业务模型变多后，每个模型的业务方法中都要进行设置，重复代码太多。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;那我们知道这种方式存在问题以后，就得找找有什么好方法对不对，往下看！&lt;/p&gt;
 &lt;h2&gt;优雅做法&lt;/h2&gt;
 &lt;p&gt;因为我们持久层框架更多地使用MyBatis，那我们就借助于MyBatis的拦截器来完成我们的功能。&lt;/p&gt;
 &lt;p&gt;首先我们来了解一下，什么是拦截器？&lt;/p&gt;
 &lt;h2&gt;什么是拦截器？&lt;/h2&gt;
 &lt;p&gt;MyBatis的拦截器顾名思义，就是对某些操作进行拦截。通过拦截器可以对某些方法执行前后进行拦截，添加一些处理逻辑。&lt;/p&gt;
 &lt;p&gt;MyBatis的拦截器可以对Executor、StatementHandler、PameterHandler和ResultSetHandler 接口进行拦截，也就是说会对这4种对象进行代理。&lt;/p&gt;
 &lt;p&gt;拦截器设计的初衷就是为了让用户在MyBatis的处理流程中不必去修改MyBatis的源码，能够以插件的方式集成到整个执行流程中。&lt;/p&gt;
 &lt;p&gt;比如MyBatis中的  &lt;code&gt;Executor&lt;/code&gt;有  &lt;code&gt;BatchExecutor&lt;/code&gt;、  &lt;code&gt;ReuseExecutor&lt;/code&gt;、  &lt;code&gt;SimpleExecutor&lt;/code&gt;和  &lt;code&gt;CachingExecutor&lt;/code&gt;，如果这几种实现的  &lt;code&gt;query&lt;/code&gt;方法都不能满足你的需求，我们可以不用去直接修改MyBatis的源码，而通过建立拦截器的方式，拦截  &lt;code&gt;Executor&lt;/code&gt;接口的  &lt;code&gt;query&lt;/code&gt;方法，在拦截之后，实现自己的query方法逻辑。&lt;/p&gt;
 &lt;p&gt;在MyBatis中的拦截器通过Interceptor接口表示，该接口中有三个方法。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  Object plugin(Object target);

  void setProperties(Properties properties);

}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;plugin方法是拦截器用于封装目标对象的，通过该方法我们可以返回目标对象本身，也可以返回一个它的代理。&lt;/p&gt;
 &lt;p&gt;当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法，当然也可以调用其他方法。&lt;/p&gt;
 &lt;p&gt;setProperties方法是用于在Mybatis配置文件中指定一些属性的。&lt;/p&gt;
 &lt;h3&gt;使用拦截器更新审计字段&lt;/h3&gt;
 &lt;p&gt;那么我们应该如何通过拦截器来实现我们对审计字段赋值的功能呢？&lt;/p&gt;
 &lt;p&gt;在我们进行订单创建和修改时，本质上是通过MyBatis执行insert、update语句，MyBatis是通过Executor来处理的。&lt;/p&gt;
 &lt;p&gt;我们可以通过拦截器拦截Executor，然后在拦截器中对要插入的数据对象根据执行的语句设置insert_by,insert_time,update_by,update_time等属性值就可以了。&lt;/p&gt;
 &lt;h3&gt;自定义拦截器&lt;/h3&gt;
 &lt;p&gt;自定义  &lt;code&gt;Interceptor&lt;/code&gt;最重要的是要实现  &lt;code&gt;plugin&lt;/code&gt;方法和  &lt;code&gt;intercept&lt;/code&gt;方法。&lt;/p&gt;
 &lt;p&gt;在  &lt;code&gt;plugin&lt;/code&gt;方法中我们可以决定是否要进行拦截进而决定要返回一个什么样的目标对象。&lt;/p&gt;
 &lt;p&gt;在  &lt;code&gt;intercept&lt;/code&gt;方法就是要进行拦截的时候要执行的方法。&lt;/p&gt;
 &lt;p&gt;对于  &lt;code&gt;plugin&lt;/code&gt;方法而言，其实Mybatis已经为我们提供了一个实现。Mybatis中有一个叫做  &lt;code&gt;Plugin&lt;/code&gt;的类，里面有一个静态方法  &lt;code&gt;wrap(Object target,Interceptor interceptor)&lt;/code&gt;，通过该方法可以决定要返回的对象是目标对象还是对应的代理。&lt;/p&gt;
 &lt;p&gt;但是这里还存在一个问题，就是我们如何在拦截器中知道要插入的表有审计字段需要处理呢？&lt;/p&gt;
 &lt;p&gt;因为我们的表中并不是所有的表都是业务表，可能有一些字典表或者定义表是没有审计字段的，这样的表我们不需要在拦截器中进行处理。&lt;/p&gt;
 &lt;p&gt;也就是说  &lt;strong&gt;我们要能够区分出哪些对象需要更新审计字段&lt;/strong&gt;。&lt;/p&gt;
 &lt;p&gt;这里我们可以定义一个接口，让需要更新审计字段的模型都统一实现该接口，这个接口起到一个标记的作用。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public interface BaseDO {
}

public class Order implements BaseDO{

    private Long orderId;

    private String orderNo;

    private Integer orderStatus;

    private String insertBy;

    private String updateBy;

    private Date insertTime;

    private Date updateTime;
    //... getter ,setter
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;接下来，我们就可以实现我们的自定义拦截器了。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;@Component(&amp;quot;ibatisAuditDataInterceptor&amp;quot;)
@Intercepts({@Signature(method = &amp;quot;update&amp;quot;, type = Executor.class, args = {MappedStatement.class, Object.class})})
public class IbatisAuditDataInterceptor implements Interceptor {

    private Logger logger = LoggerFactory.getLogger(IbatisAuditDataInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 从上下文中获取用户名
        String userName = AppContext.getUser();
        
        Object[] args = invocation.getArgs();
        SqlCommandType sqlCommandType = null;
        
        for (Object object : args) {
            // 从MappedStatement参数中获取到操作类型
            if (object instanceof MappedStatement) {
                MappedStatement ms = (MappedStatement) object;
                sqlCommandType = ms.getSqlCommandType();
                logger.debug(&amp;quot;操作类型： {}&amp;quot;, sqlCommandType);
                continue;
            }
            // 判断参数是否是BaseDO类型
            // 一个参数
            if (object instanceof BaseDO) {
                if (SqlCommandType.INSERT == sqlCommandType) {
                    Date insertTime = new Date();
                    BeanUtils.setProperty(object, &amp;quot;insertedBy&amp;quot;, userName);
                    BeanUtils.setProperty(object, &amp;quot;insertTimestamp&amp;quot;, insertTime);
                    BeanUtils.setProperty(object, &amp;quot;updatedBy&amp;quot;, userName);
                    BeanUtils.setProperty(object, &amp;quot;updateTimestamp&amp;quot;, insertTime);
                    continue;
                }
                if (SqlCommandType.UPDATE == sqlCommandType) {
                    Date updateTime = new Date();
                    BeanUtils.setProperty(object, &amp;quot;updatedBy&amp;quot;, userName);
                    BeanUtils.setProperty(object, &amp;quot;updateTimestamp&amp;quot;, updateTime);
                    continue;
                }
            }
            // 兼容MyBatis的updateByExampleSelective(record, example);
            if (object instanceof ParamMap) {
                logger.debug(&amp;quot;mybatis arg: {}&amp;quot;, object);
                @SuppressWarnings(&amp;quot;unchecked&amp;quot;)
                ParamMap&amp;lt;Object&amp;gt; parasMap = (ParamMap&amp;lt;Object&amp;gt;) object;
                String key = &amp;quot;record&amp;quot;;
                if (!parasMap.containsKey(key)) {
                    continue;
                }
                Object paraObject = parasMap.get(key);
                if (paraObject instanceof BaseDO) {
                    if (SqlCommandType.UPDATE == sqlCommandType) {
                        Date updateTime = new Date();
                        BeanUtils.setProperty(paraObject, &amp;quot;updatedBy&amp;quot;, userName);
                        BeanUtils.setProperty(paraObject, &amp;quot;updateTimestamp&amp;quot;, updateTime);
                        continue;
                    }
                }
            }
            // 兼容批量插入
            if (object instanceof DefaultSqlSession.StrictMap) {
                logger.debug(&amp;quot;mybatis arg: {}&amp;quot;, object);
                @SuppressWarnings(&amp;quot;unchecked&amp;quot;)
                DefaultSqlSession.StrictMap&amp;lt;ArrayList&amp;lt;Object&amp;gt;&amp;gt; map = (DefaultSqlSession.StrictMap&amp;lt;ArrayList&amp;lt;Object&amp;gt;&amp;gt;) object;
                String key = &amp;quot;collection&amp;quot;;
                if (!map.containsKey(key)) {
                    continue;
                }
                ArrayList&amp;lt;Object&amp;gt; objs = map.get(key);
                for (Object obj : objs) {
                    if (obj instanceof BaseDO) {
                        if (SqlCommandType.INSERT == sqlCommandType) {
                            Date insertTime = new Date();
                            BeanUtils.setProperty(obj, &amp;quot;insertedBy&amp;quot;, userName);
                            BeanUtils.setProperty(obj, &amp;quot;insertTimestamp&amp;quot;, insertTime);
                            BeanUtils.setProperty(obj, &amp;quot;updatedBy&amp;quot;, userName);
                            BeanUtils.setProperty(obj, &amp;quot;updateTimestamp&amp;quot;, insertTime);
                        }
                        if (SqlCommandType.UPDATE == sqlCommandType) {
                            Date updateTime = new Date();
                            BeanUtils.setProperty(obj, &amp;quot;updatedBy&amp;quot;, userName);
                            BeanUtils.setProperty(obj, &amp;quot;updateTimestamp&amp;quot;, updateTime);
                        }
                    }
                }
            }
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;通过上面的代码可以看到，我们自定义的拦截器  &lt;code&gt;IbatisAuditDataInterceptor&lt;/code&gt;实现了  &lt;code&gt;Interceptor&lt;/code&gt;接口。&lt;/p&gt;
 &lt;p&gt;在我们拦截器上的  &lt;code&gt;@Intercepts&lt;/code&gt;注解，  &lt;code&gt;type&lt;/code&gt;参数指定了拦截的类是  &lt;code&gt;Executor&lt;/code&gt;接口的实现，  &lt;code&gt;method&lt;/code&gt; 参数指定拦截  &lt;code&gt;Executor&lt;/code&gt;中的  &lt;code&gt;update&lt;/code&gt;方法，因为数据库操作的增删改操作都是通过  &lt;code&gt;update&lt;/code&gt;方法执行。&lt;/p&gt;
 &lt;h3&gt;配置拦截器插件&lt;/h3&gt;
 &lt;p&gt;在定义好拦截器之后，需要将拦截器指定到  &lt;code&gt;SqlSessionFactoryBean&lt;/code&gt;的  &lt;code&gt;plugins&lt;/code&gt;中才能生效。所以要按照如下方式配置。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;lt;bean id=&amp;quot;transSqlSessionFactory&amp;quot; class=&amp;quot;org.mybatis.spring.SqlSessionFactoryBean&amp;quot;&amp;gt;
    &amp;lt;property name=&amp;quot;dataSource&amp;quot; ref=&amp;quot;transDataSource&amp;quot; /&amp;gt;
    &amp;lt;property name=&amp;quot;mapperLocations&amp;quot;&amp;gt;
        &amp;lt;array&amp;gt;
            &amp;lt;value&amp;gt;classpath:META-INF/mapper/*.xml&amp;lt;/value&amp;gt;
        &amp;lt;/array&amp;gt;
    &amp;lt;/property&amp;gt;
    &amp;lt;property name=&amp;quot;plugins&amp;quot;&amp;gt;
        &amp;lt;array&amp;gt;
            &amp;lt;!-- 处理审计字段 --&amp;gt;
            &amp;lt;ref bean=&amp;quot;ibatisAuditDataInterceptor&amp;quot; /&amp;gt;
        &amp;lt;/array&amp;gt;
    &amp;lt;/property&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;到这里，我们自定义的拦截器就生效了，通过测试你会发现，不用在业务代码中手动设置审计字段的值，会在事务提交之后，通过拦截器插件自动对审计字段进行赋值。&lt;/p&gt;
 &lt;h2&gt;小结&lt;/h2&gt;
 &lt;p&gt;在本期内容中小黑给大家介绍了对于我们日常开发中很频繁的审计字段的更新操作，应该如何优雅地处理。&lt;/p&gt;
 &lt;p&gt;通过自定义MyBatis的拦截器，以插件的形式对一些有审计字段的业务模型自动赋值，避免重复编写枯燥的重复代码。&lt;/p&gt;
 &lt;p&gt;毕竟人生苦短，少写代码，多摸鱼。&lt;/p&gt;
 &lt;p&gt;如果本文对你有所帮助，给小黑点个赞鼓励下。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;我是小黑，一名在互联网“苟且”的程序员&lt;/p&gt;
  &lt;p&gt;   &lt;strong&gt;流水不争先，贵在滔滔不绝&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62101-mybatis-%E6%97%B6%E9%97%B4</guid>
      <pubDate>Sat, 05 Feb 2022 15:44:09 CST</pubDate>
    </item>
    <item>
      <title>@cozicoke：提醒一下大家，最近如果外出去公共场所的话一定要戴口罩。根据最新的排摸口径，疾控现在做流调的时候都是看监控回放的，如果是在和密接在同一空间接触的，没戴口罩那就是次密，集中隔离14天，戴口罩就是高筛，2+12管理。基本上戴口罩可以下降一个管理级别。所以大家外出千万记得戴口罩。</title>
      <link>https://itindex.net/detail/62025-cozicoke-%E5%A4%A7%E5%AE%B6-%E6%9C%80%E8%BF%91</link>
      <description>&lt;p&gt;@cozicoke：提醒一下大家，最近如果外出去公共场所的话一定要戴口罩。根据最新的排摸口径，疾控现在做流调的时候都是看监控回放的，如果是在和密接在同一空间接触的，没戴口罩那就是次密，集中隔离14天，戴口罩就是高筛，2+12管理。基本上戴口罩可以下降一个管理级别。所以大家外出千万记得戴口罩。  &lt;br /&gt;  &lt;br /&gt;想看更多劲爆内容，请下载抽屉新热榜客户端 :  &lt;br /&gt;http://dig.chouti.com/qrcode/10036&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>42区</category>
      <guid isPermaLink="true">https://itindex.net/detail/62025-cozicoke-%E5%A4%A7%E5%AE%B6-%E6%9C%80%E8%BF%91</guid>
      <pubDate>Fri, 14 Jan 2022 23:27:49 CST</pubDate>
    </item>
    <item>
      <title>英美网民在 TikTok 上平均花费的时间超过了 YouTube</title>
      <link>https://itindex.net/detail/61761-%E7%BE%8E%E7%BD%91-tiktok-%E5%B9%B3%E5%9D%87</link>
      <description>来自 App Annie 的报告称，英美网民在 TikTok 上 &lt;a href="https://www.bbc.com/news/technology-58464745" target="_blank"&gt;平均花费的时间超过了 YouTube&lt;/a&gt;。YouTube 有更多的用户，因此网民在 YouTube 上投入的总时间仍然超过 TikTok。YouTube 有大约 20 亿月活用户，而 TikTok 最新数据未知，2020 年中的数据是 7 亿。App Annie 的报告跟踪的是只是 Android 平台，也不涵盖中国，TikTok 不为中国网民提供服务，但它的中国版本抖音也极其受欢迎。 &lt;div&gt;
  &lt;a href="http://feeds.feedburner.com/~ff/solidot?a=rNOIHI-VJVA:ekrCKjPiJO8:yIl2AUoC8zA"&gt;   &lt;img border="0" src="http://feeds.feedburner.com/~ff/solidot?d=yIl2AUoC8zA"&gt;&lt;/img&gt;&lt;/a&gt;   &lt;a href="http://feeds.feedburner.com/~ff/solidot?a=rNOIHI-VJVA:ekrCKjPiJO8:7Q72WNTAKBA"&gt;   &lt;img border="0" src="http://feeds.feedburner.com/~ff/solidot?d=7Q72WNTAKBA"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/61761-%E7%BE%8E%E7%BD%91-tiktok-%E5%B9%B3%E5%9D%87</guid>
      <pubDate>Mon, 06 Sep 2021 23:29:01 CST</pubDate>
    </item>
    <item>
      <title>每周工作超55小时或心脏病致死，来看看世界公认的最健康作息时间表参考</title>
      <link>https://itindex.net/detail/61436-%E5%B7%A5%E4%BD%9C-%E5%BF%83%E8%84%8F%E7%97%85-%E7%9C%8B%E7%9C%8B</link>
      <description>&lt;p&gt;世界卫生组织和国际劳工组织17日发布报告说，工作时间过长造成全球数十万人死于中风或心脏病，新冠疫情居家办公恐怕会令这一趋势恶化。&lt;/p&gt; &lt;p&gt;报告显示，每周工作至少55个小时导致全球74.5万人在2016年死于中风和局部缺血性心脏病，较2000年增长29%。这些人去世时年龄在60岁至79岁不等，他们在45岁至74岁期间每周工作55个小时或更长时间。长时间工作致死者中72%是男性。&lt;/p&gt; &lt;p&gt;报告分析154个国家和地区数据发现，相比每周工作35个小时至40个小时，每周工作55个小时或更长时间者死于中风的风险高35%，死于局部缺血性心脏病的风险高17%。&lt;/p&gt; &lt;p&gt;报告说，就地区而言，东南亚和西太平洋地区因长时间工作致死的情况更多。研究报告刊载于12日出版的美国杂志《国际环境》。&lt;/p&gt; &lt;p&gt;相关研究在2000年至2016年展开，未涉及新冠疫情以来情况。然而，世卫组织担心，超长时间工作的趋势自疫情以来会恶化，因为居家办公可能延长工作时间。&lt;/p&gt; &lt;h4&gt;来看看世界公认的最健康作息时间表参考&lt;/h4&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;7:00：起床&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;英国威斯敏斯特大学的研究人员发现，那些在早上5:22―7:00 分起床的人，其血液中有一种能引起心脏病的物质含量较高，因此，在7:00之后起床对身体健康更加有益。打开台灯。“一醒来，就将灯打开，这样将会重新调整体内的生物钟，调整睡眠和醒来模式。”拉夫堡大学睡眠研究中心教授吉姆•霍恩说。喝一杯水。水是身体内成千上万化学反应得以进行的必需物质。早上喝一杯清水，可以补充晚上的缺水状态。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;7:00―7:20：在早饭之前刷牙&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;“在早饭之前刷牙可以防止牙齿的腐蚀，因为刷牙之后，可以在牙齿外面涂上一层含氟的保护层。要么，就等早饭之后半小时再刷牙。”英国牙齿协会健康和安全研究人员戈登•沃特金斯说。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;7:20―8:00：吃早饭&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;“早饭必须吃，因为它可以帮助你维持血糖水平的稳定。”伦敦大学国王学院营养师凯文•威尔伦说。早饭可以吃燕麦粥等，这类食物具有较低的血糖指数。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;8:30―9:00：避免运动&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;来自布鲁奈尔大学的研究人员发现，在早晨进行锻炼的运动员更容易感染疾病，因为免疫系统在这个时间的功能最弱。步行上班。马萨诸塞州大学医学院的研究人员发现，每天走路的人，比那些久坐不运动的人患感冒病的几率低25%。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;9:30：开始一天中最困难的工作&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;纽约睡眠中心的研究人员发现，大部分人在每天醒来的一两个小时内头脑最清醒。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;10:30：让眼睛离开屏幕休息一下&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;如果你使用电脑工作，那么每工作一小时，就让眼睛休息3分钟。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;11:00：吃点水果&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;这是一种解决身体血糖下降的好方法。吃一个橙子或一些红色水果，这样做能同时补充体内的铁含量和维生素C含量。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;12:30：在面包上加一些豆类蔬菜&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;你需要一顿可口的午餐，并且能够缓慢地释放能量。“烘烤的豆类食品富含纤维素，番茄酱可以当作是蔬菜的一部分。”维伦博士说。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;13:00―14:00：午休一小会儿&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;雅典的一所大学研究发现，那些每天中午午休30分钟或更长时间，每周至少午休3次的人，因心脏病死亡的几率会下降37%。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;16:00：喝杯酸奶&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;这样做可以稳定血糖水平。在每天三餐之间喝些酸牛奶，有利于心脏健康。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;17:00―19:00：锻炼身体&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;根据体内的生物钟，这个时间是运动的最佳时间，舍菲尔德大学运动学医生瑞沃•尼克说。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;19:30：晚餐少吃点&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;晚饭吃太多，会引起血糖升高，并增加消化系统的负担，影响睡眠。晚饭应该多吃蔬菜，少吃富含卡路里和蛋白质的食物。吃饭时要细嚼慢咽。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;20:00：看会电视或学习&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;这个时间看会儿电视放松一下，有助于睡眠，但要注意，尽量不要躺在床上看电视，这会影响睡眠质量。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;22:00：洗个热水澡&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;“体温的适当降低有助于放松和睡眠。”拉夫堡大学睡眠研究中心吉姆•霍恩教授说。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;22:30：上床睡觉&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;如果你早上7点起床，现在入睡可以保证你享受充足的睡眠。&lt;/p&gt; &lt;p&gt;任何试图更改生物钟的行为，都将给身体留下莫名其妙的疾病，35年之后再后悔，已经来不及了。&lt;/p&gt; &lt;p&gt;&lt;/p&gt;
                 &lt;p&gt;（编辑：梁宇芳）&lt;/p&gt;
                
                                
                
                
            &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/61436-%E5%B7%A5%E4%BD%9C-%E5%BF%83%E8%84%8F%E7%97%85-%E7%9C%8B%E7%9C%8B</guid>
      <pubDate>Tue, 18 May 2021 21:42:23 CST</pubDate>
    </item>
    <item>
      <title>Oracle数据库给字段设置默认时间及更新字段之后时间更新 - 牧雨 - 博客园</title>
      <link>https://itindex.net/detail/61434-oracle-%E6%95%B0%E6%8D%AE%E5%BA%93-%E6%97%B6%E9%97%B4</link>
      <description>&lt;div&gt;    &lt;p&gt;一、给字段设置默认时间&lt;/p&gt;    &lt;p&gt;1、建表时运用 DEFAULT SYSDATE 给字段设置默认时间：&lt;/p&gt;    &lt;div&gt;      &lt;pre&gt;CREATETABLE&amp;quot;TEST&amp;quot;.&amp;quot;TEST_DATE&amp;quot; (
idVARCHAR2(2BYTE)NOTNULL,valuesNUMBERNOTNULL,
create_time DATEDEFAULTSYSDATE,
update_timeTIMESTAMP(6)DEFAULTSYSDATE
)。&lt;/pre&gt;&lt;/div&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;2、运用 alter table 来给字段添加默认值：&lt;/p&gt;    &lt;div&gt;      &lt;pre&gt;altertableTEST_DATEadd&amp;quot;creat_time&amp;quot; DATEDEFAULTSYSDATE;&lt;/pre&gt;&lt;/div&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;其中：&lt;/p&gt;    &lt;p&gt;TEST_DATE 为表名。&lt;/p&gt;    &lt;p&gt;&amp;quot;creat_time&amp;quot;为具体字段名。&lt;/p&gt;    &lt;p&gt;DATE :为字段类型。&lt;/p&gt;    &lt;p&gt;注意所选字段为当前表的字段，且字段正确性要验证，否则会多添加出一个字段 。&lt;/p&gt;    &lt;p&gt;二、字段更新后自动更新update_time.&lt;/p&gt;    &lt;p&gt;　　通过给表设置触发器，当触发器触发时则会自动调用触发条件：&lt;/p&gt;    &lt;p&gt;　　&lt;/p&gt;    &lt;div&gt;      &lt;pre&gt;createorreplacetriggerTEST_DATE_trigger
beforeupdateonTEST_DATEforeach rowbegin:new.UPDATE_TIME :=sysdate;end;&lt;/pre&gt;&lt;/div&gt;    &lt;p&gt;其中：&lt;/p&gt;    &lt;p&gt;TEST_DATE_trigger 为触发器名称。&lt;/p&gt;    &lt;p&gt;TEST_DATE ：为表名&lt;/p&gt;    &lt;p&gt;UPDATE_TIME：为字段名&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/61434-oracle-%E6%95%B0%E6%8D%AE%E5%BA%93-%E6%97%B6%E9%97%B4</guid>
      <pubDate>Tue, 18 May 2021 19:34:46 CST</pubDate>
    </item>
    <item>
      <title>高并发下高效获取时间戳</title>
      <link>https://itindex.net/detail/61319-%E5%B9%B6%E5%8F%91-%E6%97%B6%E9%97%B4%E6%88%B3</link>
      <description>&lt;div&gt;  &lt;h1&gt;背景&lt;/h1&gt;
  &lt;p&gt;Java获取当前时间戳一般是通过System.currentTimeMillis()来获取。这是一个native方法，用于获取当前时间与1970年1月1日0点之间的差，虽然返回值的时间单位是毫秒，但该值的粒度取决于基础操作系统&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1bbfb9874b85404abb2d85aceed0772f~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;看下HotSpot源码的hotspot/src/os/linux/vm/os_linux.cpp文件中，有一个javaTimeMillis()方法，这就是System.currentTimeMillis()的native实现&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;jlong os::javaTimeMillis() {

timeval time;

int status = gettimeofday(&amp;amp;time, NULL);

assert(status != -1, &amp;quot;linux error&amp;quot;);

return jlong(time.tv_sec) * 1000 + jlong(time.tv_usec / 1000);

}
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;可以看到：&lt;/p&gt;
  &lt;ul&gt;
   &lt;li&gt;
    &lt;p&gt;调用gettimeofday()需要从用户态切换到内核态；&lt;/p&gt;
&lt;/li&gt;
   &lt;li&gt;
    &lt;p&gt;gettimeofday()的表现受Linux系统的计时器（时钟源）影响，在HPET计时器下性能尤其差；&lt;/p&gt;
&lt;/li&gt;
   &lt;li&gt;
    &lt;p&gt;系统只有一个全局时钟源，高并发或频繁访问会造成严重的争用。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
  &lt;p&gt;HPET计时器性能较差的原因是会将所有对时间戳的请求串行执行。TSC计时器性能较好，因为有专用的寄存器来保存时间戳。缺点是可能不稳定，因为它是纯硬件的计时器，频率可变（与处理器的CLK信号有关）&lt;/p&gt;
  &lt;p&gt;可以用以下的命令查看时钟源：&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;cat /sys/devices/system/clocksource/clocksource0/available_clocksource # 查看全部支持的时钟源
cat /sys/devices/system/clocksource/clocksource0/current_clocksource # 查看当前使用的时钟源
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b4ea1c77aee542cab0c73c0801b92748~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;修改时钟源：&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;echo &amp;apos;hpet&amp;apos; &amp;gt; /sys/devices/system/clocksource/clocksource0/current_clocksource
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;h1&gt;解决方案&lt;/h1&gt;
  &lt;p&gt;最常见的办法是用单个调度线程（守护线程）来按毫秒更新时间戳，相当于维护一个全局内存缓存。&lt;/p&gt;
  &lt;p&gt;其他线程取时间戳时相当于从内存取，不会再造成时钟资源的争用，代价就是牺牲了一些精确度。&lt;/p&gt;
  &lt;p&gt;代码如下：&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;package cn.hutool.core.date;

import java.sql.Timestamp;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 系统时钟    &lt;br /&gt;
 * 高并发场景下System.currentTimeMillis()的性能问题的优化
 * System.currentTimeMillis()的调用比new一个普通对象要耗时的多（具体耗时高出多少我还没测试过，有人说是100倍左右）
 * System.currentTimeMillis()之所以慢是因为去跟系统打了一次交道
 * 后台定时更新时钟，JVM退出时，线程自动回收
 */
public class SystemClock {

/** 时钟更新间隔，单位毫秒 */
private final long period;
/** 现在时刻的毫秒数 */
private volatile long now;

/**
 * 构造
 * @param period 时钟更新间隔，单位毫秒
 */
public SystemClock(long period) {
this.period = period;
this.now = System.currentTimeMillis();
scheduleClockUpdating();
}

/**
 * 开启计时器线程
 */
private void scheduleClockUpdating() {
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -&amp;gt; {
Thread thread = new Thread(runnable, &amp;quot;System Clock&amp;quot;);
thread.setDaemon(true);
return thread;
});
scheduler.scheduleAtFixedRate(() -&amp;gt; now = System.currentTimeMillis(), period, period, TimeUnit.MILLISECONDS);
}

/**
 * @return 当前时间毫秒数
 */
private long currentTimeMillis() {
return now;
}

//------------------------------------------------------------------------ static
/**
 * 单例
 *
 */
private static class InstanceHolder {
public static final SystemClock INSTANCE = new SystemClock(1);
}

/**
 * @return 当前时间
 */
public static long now() {
return InstanceHolder.INSTANCE.currentTimeMillis();
}

/**
 * @return 当前时间字符串表现形式
 */
public static String nowDate() {
return new Timestamp(InstanceHolder.INSTANCE.currentTimeMillis()).toString();
}
}
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;ul&gt;
   &lt;li&gt;采用守护线程作为定时任务：守护线程会随着用户线程的结束而结束，无需单独处理关闭问题（程序结束时，守护线程可以立即关闭，不同于用户线程，比如数据库操作线程，正在插入数据的时候，程序结束了，此时必须正常正确的优雅结束插入数据线程），为其他用户线程提供服务支持&lt;/li&gt;
   &lt;li&gt;定时任务每毫秒调用一次System.currentTimeMillis()，并存入内存中，采用volatile保证不同线程间的可见性（由于采用了volatile所以禁止了重排序，导致CPU三级缓存无法用到，多线程单次调用下可能性能不佳，后面讲到）&lt;/li&gt;
   &lt;li&gt;单例模式，防止new多个对象，导致起多个定时获取时间戳的守护线程&lt;/li&gt;
&lt;/ul&gt;
  &lt;h1&gt;使用&lt;/h1&gt;
  &lt;ul&gt;
   &lt;li&gt;根据时间戳进行耗时分析或者只是获取当前时间的场景&lt;/li&gt;
   &lt;li&gt;日志打印（access.log，log4j日志）中日志时间的写入&lt;/li&gt;
   &lt;li&gt;new Date()获取当前时间的场景&lt;/li&gt;
&lt;/ul&gt;
  &lt;h3&gt;其中日志的时间打印&lt;/h3&gt;
  &lt;h4&gt;LOG4J篇：&lt;/h4&gt;
  &lt;p&gt;我们采用Lombok的日志注解，启动一个单元测试，走一下看看我们平常用的日志框架是如何注入时间戳的&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c78cd48d83d5424db4ab9069d1d9d4c8~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;如上图，进入断点，选择步入方法（进入方法内部）&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a859e1b733bb44389543143e8b267587~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;接着步入&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2262933746a14b8994412311bce7bef6~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;如上图，选择步过（跳过其他方法，进入该方法的下一步）&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cbbb2f6f2ea04ebbb3d2ca2c4ef8e504~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/31870e77721a4478b917a3cd04cf0640~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;如上图，选择步入后，选择要步入的方法&lt;/p&gt;
  &lt;p&gt;接着按照次方法一步一步走......&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2e6fd47ed8f744e1821f6d3583d84515~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;走到上图这里后，我们发下这边会返回一个logEvent，这个logEvent涉及到时间戳&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0d0ddecd62114c5193803476a03023bb~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;所以这个logEventFactory对象是怎么初始化的就很重要&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/537cb552d3d8402ea7941f554a1e440a~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;如上图，我们当前类搜索logEventFactory，结合构造函数已经静态类，可以看到这个logEnvetFactory工厂类是通过log4j的配置文件log4j2.component.properties获取，进行初始化&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/eee7127d6f1e428eafe2be812f50ffb0~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/73c168b3644948d39c474904668aebe5~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b0a7c9cf860448afb6f04bcf1cc5cd94~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/169da63047344988a9572f6a729eacd8~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;如下图，看下默认的LogEvent工厂&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bd0ca633385a4c13997dd4fbc9c25169~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3f7b32cd399846b79659372f966979ca~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0ed2ec26319442a285ec5b5caf84c56f~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;如下图，可以看到纳秒时间的获取默认是获取一个假的时间，时间戳是0，无需关注&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5e97ae721a19407eb8dd77c49e65db91~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;我们需要关注CLOCK这个对象的获取（通过工厂类获取）&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fecf2b5c693641d59cc235b41149727c~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;我们看下getStringProperty方法，如下图，通过environment的get获取，我们来看下这个get方法&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f7567411010a494fa206d76e344f5c0e~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;如上图，我们可以看到，除了在配置文件（log4j2.component.properties）外，这个属性的key（log4j.Clock）是可以通过java启动变量来注入的，类似 -Dlog4j.Clock=自定义的类路径&lt;/p&gt;
  &lt;p&gt;之后，我们需要实现Clock的currentTimeMillis接口，如下图&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/87989d10af07477db02f6e5212bd4e53~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;如下图，只需实现这个接口就好，之后把这个类路径注入到启动参数中，log4j就会用这个获取时间的方式&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;public class SystemLogClock implements Clock {

  @Override
  public long currentTimeMillis() {
    // 改为从内存获取
    return SystemClock.now();
  }
}
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;除此之外就没有了吗，当然不是，还需要注意一个地方，我们接着之前LoggerConfig的断点往下走，如下图&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/281cc5c6317c4dec87fc0aafab7c0596~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b60cb3fa8a3c421b9c1c31fea56c7281~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;如上图，看到Layout，大家应该不陌生，通过继承AbstractStringLayout实现toSerializable方法来进行格式化输出。&lt;/p&gt;
  &lt;p&gt;此外还需要   &lt;a href="https://my.oschina.net/u/1777377" rel="nofollow noopener noreferrer" target="_blank"&gt;@Plugin&lt;/a&gt;注解进行注册&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;@Plugin(name = &amp;quot;CustomLayout&amp;quot;, category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;还需要在log4j2-dev.xml这样的配置文件中进行注入，类似&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;











复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;在自定义的CustomLayout中实现toSerializable时，我们可以使用从内存中获取的时间戳进行赋值&lt;/p&gt;
  &lt;h4&gt;ACCESS.LOG篇：&lt;/h4&gt;
  &lt;p&gt;同log4j的分析，我们需要打断点来分析注入时间戳的地方，以undertow为例&lt;/p&gt;
  &lt;p&gt;我们知道undertow有个配置文件的地方，类似：&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;server:
  port: 8888
  undertow:
    # 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
    # 每块buffer的空间大小,越小的空间被利用越充分
    buffer-size: 1024
    # 是否分配的直接内存
    direct-buffers: true
    accesslog:
      dir: &amp;quot;/logs&amp;quot;
      enabled: true
      pattern: &amp;apos;%h %l %u %t &amp;quot;%r&amp;quot; %s %b&amp;apos;
      prefix: &amp;quot;custom.access.log&amp;quot;
      suffix: &amp;quot;&amp;quot;
    threads:
      worker: 4096
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;观察上图的pattern，我们注意到有个%t 这个占位符，这个正好对应请求的时间戳的位置，类似&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;0:0:0:0:0:0:0:1 - - 2021-04-08 16:01:21.867 &amp;quot;GET /custom?token=aaaa HTTP/1.1&amp;quot; 200 1316
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;那么可以自定义一个占位符来更改获取时间戳的逻辑&lt;/p&gt;
  &lt;p&gt;结合我的上一篇文章 Undertow容器在Springboot中如何自定义修改文件名    &lt;a href="https://juejin.cn/post/6930609008898211854%C2%A0" target="_blank"&gt;juejin.cn/post/693060…&lt;/a&gt; 可以知道修改获取时间戳的地方&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/37ca541f734943ba90499b1760e64501~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7c5b87beb8f14ba3b65275efa7fb3181~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d359bf02e29c493b9ac335c8a0773b9d~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;我们只需要继承ExchangeAttribute接口，实现readAttribute方法就可以&lt;/p&gt;
  &lt;p&gt;再来看下builders的获取，如下图：&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ac86527e39de4e5b97a6e5cc0d083ca0~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/eb0e319f720e42559ac36a7ca1031d9d~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;同样我们可以参照io.undertow.attribute.DateTimeAttribute 来进行重写，修改时间戳的获取方式，并自定义占位符前缀 %{customTime, 如下图：&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c0443435834a499faf6c094553d8ac20~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;package com.xiaoju.manhattan.messenger.notice.api.config.undertow;

import cn.hutool.core.date.SystemClock;
import io.undertow.attribute.ExchangeAttribute;
import io.undertow.attribute.ExchangeAttributeBuilder;
import io.undertow.attribute.ReadOnlyAttributeException;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.DateUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

/**
 * The current time
 *
 * @author Stuart Douglas
 */
public class CustomDateTimeAttribute implements ExchangeAttribute {

  public static final String CUSTOM_TIME = &amp;quot;%{customTime,&amp;quot;;

  public static final ExchangeAttribute INSTANCE = new CustomDateTimeAttribute();

  private final String dateFormat;
  private final ThreadLocal cachedFormat;

  private CustomDateTimeAttribute() {
    this.dateFormat = null;
    this.cachedFormat = null;
  }

  public CustomDateTimeAttribute(final String dateFormat) {
    this(dateFormat, null);
  }

  public CustomDateTimeAttribute(final String dateFormat, final String timezone) {
    this.dateFormat = dateFormat;
    this.cachedFormat = ThreadLocal.withInitial(() -&amp;gt; {
      final SimpleDateFormat format = new SimpleDateFormat(dateFormat);
      if (timezone != null) {
        format.setTimeZone(TimeZone.getTimeZone(timezone));
      }
      return format;
    });
  }

  @Override
  public String readAttribute(final HttpServerExchange exchange) {
    if (dateFormat == null) {
      return DateUtils.toCommonLogFormat(new Date(SystemClock.now()));
    } else {
      final SimpleDateFormat dateFormat = this.cachedFormat.get();
      return dateFormat.format(new Date(SystemClock.now()));
    }
  }

  @Override
  public void writeAttribute(final HttpServerExchange exchange, final String newValue)
      throws ReadOnlyAttributeException {
    throw new ReadOnlyAttributeException(&amp;quot;Date time&amp;quot;, newValue);
  }

  public static final class Builder implements ExchangeAttributeBuilder {

    @Override
    public String name() {
      return &amp;quot;Date Time&amp;quot;;
    }

    @Override
    public ExchangeAttribute build(final String token) {
      if (token.startsWith(CUSTOM_TIME) &amp;amp;&amp;amp; token.endsWith(&amp;quot;}&amp;quot;)) {
        return new CustomDateTimeAttribute(
            token.substring(CUSTOM_TIME.length(), token.length() - 1));
      }
      return null;
    }

    @Override
    public int priority() {
      return 0;
    }
  }
}
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;接着仿照SPI的方式，进行注入，如下图&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;custom.undertow.CustomDateTimeAttribute$Builder
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;最后修改下我们的pattern，更改%t 为 %{customTime,yyyy-MM-dd HH:mm:ss.SSS}&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;pattern: &amp;apos;%h %l %u %{customTime,yyyy-MM-dd HH:mm:ss.SSS} &amp;quot;%r&amp;quot; %s %b&amp;apos;
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;这样就可以实现时间戳获取的替换&lt;/p&gt;
  &lt;h1&gt;性能测试&lt;/h1&gt;
  &lt;p&gt;为了测试性能，写了一个单元测试&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;package custom;

import cn.hutool.core.date.SystemClock;
import com.github.houbb.junitperf.core.annotation.JunitPerfConfig;
import java.util.concurrent.CompletableFuture;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

/**
 * @Description 系统时钟并发测试
 */
@Slf4j
public class SystemClockTest {


  /**
   * 起2000个线程进行获取当前时间戳(native调用)
   */
  @Test
  @JunitPerfConfig(threads = 2000)
  public void testSystemTime() {
    System.currentTimeMillis();
  }

  /**
   * 起2000个线程进行获取当前时间戳（内存调用）
   */
  @Test
  @JunitPerfConfig(threads = 2000)
  public void testSystemClockMultiThread() {
    SystemClock.now();
  }

  /**
   * 测试log4j日志写入流程
   */
  @Test
  public void testLog() {
    log.info(&amp;quot;欧拉欧拉欧拉欧拉欧拉欧拉欧拉欧拉&amp;quot;);
  }

  @Test
  public void systemClockTest() throws InterruptedException {
    // 预热
    long ready = SystemClock.now();
    int num = 10000;
    long start0 = System.currentTimeMillis();
    batchExe(num, () -&amp;gt; {
      for (int i = 0; i &amp;lt; 10; i++) {
        SystemClock.now();
      }
    });
    long end0 = System.currentTimeMillis();
    System.out.println(&amp;quot;pre ready SystemClock Time:&amp;quot; + (end0 - start0) + &amp;quot;毫秒&amp;quot;);

    // SystemClock
    long start1 = System.currentTimeMillis();
    batchExe(num, () -&amp;gt; {
      for (int i = 0; i &amp;lt; num; i++) {
        SystemClock.now();
      }
    });
    long end1 = System.currentTimeMillis();
    System.out.println(&amp;quot;SystemClock Time:&amp;quot; + (end1 - start1) + &amp;quot;毫秒&amp;quot;);

    // currentTimeMillis
    long start2 = System.currentTimeMillis();
    batchExe(num, () -&amp;gt; {
      for (int i = 0; i &amp;lt; num; i++) {
        System.currentTimeMillis();
      }
    });
    long end2 = System.currentTimeMillis();
    System.out.println(&amp;quot;CurrentTimeMillis Time:&amp;quot; + (end2 - start2) + &amp;quot;毫秒&amp;quot;);
  }

  private void batchExe(int num, Runnable runnable) {
    CompletableFuture[] futures = new CompletableFuture[num];
    for (int i = 0; i &amp;lt; num; i++) {
      futures[i] = CompletableFuture.runAsync(runnable);
    }

    CompletableFuture.allOf(futures).join();
  }

}
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;当有1万个线程并发调用，每个线程获取一万次时间戳的时候（进行预热，排除对象初始化，线程初始化占用的时间），如下图：&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1667fc432dfb454fa3722ddfb218af77~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;可以看到有明细的性能差距，大概有10倍的差距&lt;/p&gt;
  &lt;p&gt;当有50个线程直接调用时（native调用）：&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;[INFO] [2021-04-02 17:17:48.301] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - --------------------------------------------------------
[INFO] [2021-04-02 17:17:48.303] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Started at:  2021-04-02 17:16:48.177
[INFO] [2021-04-02 17:17:48.303] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Invocations:  67108914
[INFO] [2021-04-02 17:17:48.304] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Success:  67108914
[INFO] [2021-04-02 17:17:48.304] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Errors:  0
[INFO] [2021-04-02 17:17:48.304] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Thread Count:  50
[INFO] [2021-04-02 17:17:48.305] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Warm up:  0ms
[INFO] [2021-04-02 17:17:48.305] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Execution time:  60000ms
[INFO] [2021-04-02 17:17:48.305] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Throughput:  1118481/s (Required: -1/s) - PASSED
[INFO] [2021-04-02 17:17:48.305] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Memory cost:  16byte
[INFO] [2021-04-02 17:17:48.428] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Min latency:  1.0E-6ms (Required: -1.0ms) - PASSED
[INFO] [2021-04-02 17:17:48.512] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Max latency:  272.46924ms (Required: -1.0ms) - PASSED
[INFO] [2021-04-02 17:17:48.683] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Avg latency:  1.922781E-4ms (Required: -1.0ms) - PASSED
[INFO] [2021-04-02 17:17:48.683] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - --------------------------------------------------------
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;当有50个线程直接调用时（内存调用）&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;[INFO] [2021-04-02 17:19:08.016] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - --------------------------------------------------------
[INFO] [2021-04-02 17:19:08.017] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Started at:  2021-04-02 17:18:07.834
[INFO] [2021-04-02 17:19:08.017] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Invocations:  67108914
[INFO] [2021-04-02 17:19:08.017] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Success:  67108914
[INFO] [2021-04-02 17:19:08.018] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Errors:  0
[INFO] [2021-04-02 17:19:08.018] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Thread Count:  50
[INFO] [2021-04-02 17:19:08.018] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Warm up:  0ms
[INFO] [2021-04-02 17:19:08.019] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Execution time:  60000ms
[INFO] [2021-04-02 17:19:08.019] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Throughput:  1118481/s (Required: -1/s) - PASSED
[INFO] [2021-04-02 17:19:08.019] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Memory cost:  16byte
[INFO] [2021-04-02 17:19:08.112] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Min latency:  1.0E-6ms (Required: -1.0ms) - PASSED
[INFO] [2021-04-02 17:19:08.196] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Max latency:  179.08081ms (Required: -1.0ms) - PASSED
[INFO] [2021-04-02 17:19:08.368] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Avg latency:  1.3518152E-4ms (Required: -1.0ms) - PASSED
[INFO] [2021-04-02 17:19:08.369] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - --------------------------------------------------------
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;但是当1万个线程并发调用，每个线程获取一次时间戳的时候，如下图：&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/12fa3f36176c415cadafee254c2f2798~tplv-k3u1fbpfcp-zoom-1.image"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;当有2000个线程直接调用时（native调用）&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;[INFO] [2021-04-02 17:30:48.815] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - --------------------------------------------------------
[INFO] [2021-04-02 17:30:48.816] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Started at:  2021-04-02 17:27:32.223
[INFO] [2021-04-02 17:30:48.817] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Invocations:  67110864
[INFO] [2021-04-02 17:30:48.817] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Success:  67110864
[INFO] [2021-04-02 17:30:48.817] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Errors:  0
[INFO] [2021-04-02 17:30:48.817] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Thread Count:  2000
[INFO] [2021-04-02 17:30:48.817] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Warm up:  0ms
[INFO] [2021-04-02 17:30:48.818] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Execution time:  60000ms
[INFO] [2021-04-02 17:30:48.818] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Throughput:  1118514/s (Required: -1/s) - PASSED
[INFO] [2021-04-02 17:30:48.818] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Memory cost:  16byte
[INFO] [2021-04-02 17:30:48.926] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Min latency:  1.0E-6ms (Required: -1.0ms) - PASSED
[INFO] [2021-04-02 17:30:49.028] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Max latency:  99.11504ms (Required: -1.0ms) - PASSED
[INFO] [2021-04-02 17:30:49.209] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - Avg latency:  1.8578941E-4ms (Required: -1.0ms) - PASSED
[INFO] [2021-04-02 17:30:49.209] [c.x.m.m.n.a.s.SystemClockTest.testSystemTime] - 
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;当有2000个线程直接调用时（内存调用）&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;[INFO] [2021-04-02 17:34:26.853] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - --------------------------------------------------------
[INFO] [2021-04-02 17:34:26.856] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Started at:  2021-04-02 17:31:14.774
[INFO] [2021-04-02 17:34:26.856] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Invocations:  67110864
[INFO] [2021-04-02 17:34:26.856] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Success:  67110864
[INFO] [2021-04-02 17:34:26.856] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Errors:  0
[INFO] [2021-04-02 17:34:26.857] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Thread Count:  2000
[INFO] [2021-04-02 17:34:26.857] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Warm up:  0ms
[INFO] [2021-04-02 17:34:26.858] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Execution time:  60000ms
[INFO] [2021-04-02 17:34:26.858] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Throughput:  1118514/s (Required: -1/s) - PASSED
[INFO] [2021-04-02 17:34:26.859] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Memory cost:  16byte
[INFO] [2021-04-02 17:34:26.991] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Min latency:  1.0E-6ms (Required: -1.0ms) - PASSED
[INFO] [2021-04-02 17:34:27.089] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Max latency:  167.16472ms (Required: -1.0ms) - PASSED
[INFO] [2021-04-02 17:34:27.293] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - Avg latency:  3.9316338E-4ms (Required: -1.0ms) - PASSED
[INFO] [2021-04-02 17:34:27.293] [c.x.m.m.n.a.s.SystemClockTest.testSystemClockMultiThread] - 
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;可以看到，从内存获取反而不如直接native调用速度快&lt;/p&gt;
  &lt;p&gt;原因是：通过volatile修饰的变量，无法重排序，也无法使用写缓冲和三级缓存，为了保证可见性，只能通过主内存来获取&lt;/p&gt;
  &lt;p&gt;我们知道三级缓存是SRAM缓存，速度要比内存缓存快很多，一级缓存的话，平均速度是1ns，三级缓存也有12ns左右，而内存速度要差很多&lt;/p&gt;
  &lt;p&gt;所以多线程调用下，可能还不如直接的系统调用，例如TSC计时器性能较好，因为有专用的寄存器来保存时间戳&lt;/p&gt;
  &lt;h1&gt;注意事项&lt;/h1&gt;
  &lt;ul&gt;
   &lt;li&gt;在System.currentTimeMillis()的效率没有影响程序整体的效率时，就完全没有必要做这种优化，这只是为极端情况准备的&lt;/li&gt;
   &lt;li&gt;如果要改造获取时间戳的方式，那么日志，业务部分等的地方都需要替换下&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;  &lt;div&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/61319-%E5%B9%B6%E5%8F%91-%E6%97%B6%E9%97%B4%E6%88%B3</guid>
      <pubDate>Fri, 09 Apr 2021 08:10:44 CST</pubDate>
    </item>
    <item>
      <title>汉朝盛世比唐朝持续时间更长，为何外国人把中国人称为...</title>
      <link>https://itindex.net/detail/61176-%E6%B1%89%E6%9C%9D-%E7%9B%9B%E4%B8%96-%E6%AF%94%E5%94%90</link>
      <description>&lt;div&gt;
  &lt;div&gt;
   &lt;img alt="&amp;#27721;&amp;#26397;&amp;#30427;&amp;#19990;&amp;#27604;&amp;#21776;&amp;#26397;&amp;#25345;&amp;#32493;&amp;#26102;&amp;#38388;&amp;#26356;&amp;#38271;&amp;#65292;&amp;#20026;&amp;#20309;&amp;#22806;&amp;#22269;&amp;#20154;&amp;#25226;&amp;#20013;&amp;#22269;&amp;#20154;&amp;#31216;&amp;#20026;&amp;#8220;&amp;#21776;&amp;#20154;&amp;#8221;&amp;#65311;&amp;#30340;&amp;#22836;&amp;#22270;" src="https://iknow-pic.cdn.bcebos.com/08f790529822720ebadeae856bcb0a46f21fabb9?x-bce-process=image/resize,m_lfit,w_800,h_450,limit_1"&gt;&lt;/img&gt;
&lt;/div&gt;
  &lt;h1&gt;
汉朝盛世比唐朝持续时间更长，为何外国人把中国人称为“唐人”？
&lt;/h1&gt;
  &lt;div&gt;
2021-01-13
 | 
   &lt;a href="https://zhidao.baidu.com/daily/author?un=%E5%8E%86%E5%8F%B2%E5%A4%A7%E5%AD%A6%E5%A0%82&amp;ie=gbk" target="_blank"&gt;历史大学堂&lt;/a&gt;
原创
   &lt;input type="text" value="2586"&gt;&lt;/input&gt;
   &lt;a href="https://zhidao.baidu.com//daily/view?id=230019#"&gt;
    &lt;strong&gt;&lt;/strong&gt;
收藏(0)&lt;/a&gt;
 | 

&lt;/div&gt;
  &lt;div&gt;
   &lt;p&gt;提到汉朝和唐朝这两个盛世朝代，稍微懂一点历史的中国人就会骄傲的竖起大拇指！从盛世持续的时间长短看，汉朝盛世持续的时间比唐朝更长。但是，今天的外国人却把中国人称为“唐人”，遍布在国外的中国人聚居区被称为“唐人街”，这到底是怎么回事呢？&lt;/p&gt;   &lt;p&gt;要想回答这个问题，我们就得从两个盛世帝国对外交往的特点找到答案。&lt;/p&gt;   &lt;p&gt;    &lt;img src="https://iknow-pic.cdn.bcebos.com/503d269759ee3d6dfed3cbb453166d224f4adebc?x-bce-process=image/resize,m_lfit,w_450,h_600,limit_1"&gt;&lt;/img&gt;&lt;/p&gt;   &lt;p&gt;上图_ 张骞出塞&lt;/p&gt;   &lt;p&gt;第一，汉朝对外交往的范围比唐朝小。&lt;/p&gt;   &lt;p&gt;根据史书记载，汉朝对外交往的范围在欧亚大陆。在东亚地区，汉朝和日本列岛诸国、朝鲜半岛有一定的往来。汉武帝派张骞出使西域后，汉帝国和中亚、西亚各国，以及欧洲罗马帝国之间建立联系。遗憾的是，两汉并没有和南亚大陆的另一文明古国——印度有过多的往来。&lt;/p&gt;   &lt;p&gt;相比较汉朝，唐朝的对外交往范围就更加广泛了。作为强盛的大一统中央帝国，唐朝不仅恢复了和两汉同日本、朝鲜半岛、中亚、西亚的往来，还和南亚次大陆的印度深入交往。根据《唐六典》记载，至唐玄宗开元年间，向唐朝朝贡过的国家，已经灭亡的就有300多个，当时还有70多个国家和开元时期的盛唐保持友好往来。&lt;/p&gt;   &lt;p&gt;    &lt;img src="https://iknow-pic.cdn.bcebos.com/b3fb43166d224f4a908c3f6c19f790529822d1bc?x-bce-process=image/resize,m_lfit,w_450,h_600,limit_1"&gt;&lt;/img&gt;&lt;/p&gt;   &lt;p&gt;上图_ 唐高宗总章二年(669年)唐朝疆域图&lt;/p&gt;   &lt;p&gt;第二，唐朝对外交往是全面的交往，而汉朝对外交往仅仅是官方的政治经济往来。&lt;/p&gt;   &lt;p&gt;两汉时期尽管国力强盛，对外交往比较频繁。但我们应该注意到一点，汉朝对外交往仅仅是官方的政治经济往来。&lt;/p&gt;   &lt;p&gt;这一点可以从汉朝和日本交往找到答案。两汉时期，中国文化也开始影响日本。但是，两汉和日本列岛的交往仅限于政治经济往来。具体说来，在汉武帝征服朝鲜半岛，设置四郡后，中国文化开始影响日本。日本列岛有30多个国家向汉朝上贡献，中日之间的政治经济交往开始起步。到了东汉时期，日本倭奴国的国王派遣使者来到洛阳，汉光武帝赐予倭奴国王金印，倭奴国成为汉朝的附属国。&lt;/p&gt;   &lt;p&gt;汉朝和日本的交往仅限于政治经济层面，而唐朝和日本的交往则是全方面的交往。唐太宗和唐高宗时期，大唐国力快速提升。公元630年，日本舒明天皇就派出遣唐使来到长安，和唐朝建立官方往来。而遣唐使的意义就等于今天的日本驻中国大使。&lt;/p&gt;   &lt;p&gt;    &lt;img src="https://iknow-pic.cdn.bcebos.com/279759ee3d6d55fb1ed33a597d224f4a20a4ddbc?x-bce-process=image/resize,m_lfit,w_450,h_600,limit_1"&gt;&lt;/img&gt;&lt;/p&gt;   &lt;p&gt;上图_ 汉倭奴国王金印&lt;/p&gt;   &lt;p&gt;遣唐使建立后，日本和唐朝的交往在各个领域全面升温。首先，日本派出留学生来到长安，学习唐朝的先进政治、经济、文化和科技知识。其次，日本政府学习唐朝的三省六部制和其他制度，展开了由奴隶制社会向封建制社会的改革——大化改新。日本城市京都的规划和建筑模仿唐长安城，唐朝时期的茶道、服装对日本产生深远的影响。大唐帝国对日本的的影响力由此可见一斑。&lt;/p&gt;   &lt;p&gt;从两汉和唐朝同邻国日本的交往进行对比，我们就能看出：两汉与唐朝的对外交往，其深度是完全不同的。两汉对外交往仅限于官方往来，全是政治经济层面。而唐朝的对外交往不仅仅是政治和经济往来，文化往来也更加深入全面。&lt;/p&gt;   &lt;p&gt;两汉时期，其他国家没有向长安和洛阳派出留学生，而唐朝时期，周边国家向长安派出了一批又一批的留学生。两汉时期，首都长安（洛阳）做生意的外国人不见记载，而唐朝时期，首都长安的西市和东市全是做生意的外国人——诸多日本人、高丽人、百济人、阿拉伯人、西域诸国人、波斯人在长安做生意，长安就像今天的纽约一样，成为了一座各国人民交密切交往的国际化大都市。&lt;/p&gt;   &lt;p&gt;    &lt;img src="https://iknow-pic.cdn.bcebos.com/54fbb2fb43166d22e06259eb562309f79052d2bc?x-bce-process=image/resize,m_lfit,w_450,h_600,limit_1"&gt;&lt;/img&gt;&lt;/p&gt;   &lt;p&gt;上图_ 唐朝长安平面图&lt;/p&gt;   &lt;p&gt;两汉时期，中央政府里少有国外人做官。到了唐朝，中央政府已经全面重用外国人担任官职。比如高丽人高仙芝就曾担任武威太守、河西节度使，百济人黑齿常之因对吐蕃作战有功，在唐高宗时期在军中担任要职。日本人阿倍仲麻吕担任安南都护使，突厥人阿史那斯摩担任唐太宗时期的军中要职。到了中晚唐时期，尽管唐朝国力有些下滑，但来唐朝做官的外国人依然人数不减。比如，唐德宗时期，波斯人李元琼担任华州刺史、御史大夫。唐宣宗时期，阿拉伯大食帝国的李彦升担任翰林学士。&lt;/p&gt;   &lt;p&gt;唐朝对外频繁而全面的交往就产生了一系列正面的积极影响：通过陆上丝绸之路和海上丝绸之路，唐朝的政治、经济和文化一次次影响周边各国，在诸多外国人的心中，唐朝已然成为中国的代名词，追捧唐朝文化就等于追捧中国文化。尽管唐朝的盛世时间没有两汉长，但唐朝的海纳百川、兼容并包的心态确实超过了汉朝。&lt;/p&gt;   &lt;p&gt;从吸纳各国留学生，让外国人在首都做生意，让外国人担任官职这三点就能看出，唐朝的对外自信确实比汉朝更胜一筹，唐朝的国际影响力比汉朝要大出许多。即便唐朝于公元907年灭亡，但唐朝的影响却一直持续到今天，以至于今天的外国人称中国人为唐人。&lt;/p&gt;   &lt;p&gt;    &lt;img src="https://iknow-pic.cdn.bcebos.com/3c6d55fbb2fb4316c30a360530a4462309f7d3bc?x-bce-process=image/resize,m_lfit,w_450,h_600,limit_1"&gt;&lt;/img&gt;&lt;/p&gt;   &lt;p&gt;上图_ (古代)丝绸之路路线图&lt;/p&gt;   &lt;p&gt;第三，唐朝对外战争的获胜比汉朝更多，唐朝建立了亚欧大陆绝对的军事权威，以至于安史之乱爆发后，唐朝都得到了很多国家的军事支持。&lt;/p&gt;   &lt;p&gt;汉唐的军力都很强。但整体来看，盛唐时期的军力要比汉朝的军力更强。西汉打匈奴，刚开始由于国力不行，西汉花了好几十年的时间和匈奴和亲。到了汉武帝时期，汉朝兵强马壮，汉军将领卫青、李广、霍去病等人率领大军发动多次反击，最终在漠北之战中取得反击匈奴的决定性胜利。&lt;/p&gt;   &lt;p&gt;但是，我们必须看到一点：汉武帝反击匈奴仅仅是取得决定性胜利，匈奴并没有灭亡。直至东汉汉章帝时期，窦宪才率领大军灭亡匈奴。从西汉到东汉，汉朝花了2个多世纪的时间，彻底解决了匈奴的威胁。&lt;/p&gt;   &lt;p&gt;除了对付匈奴，汉朝在对外战争取得了进攻大宛和朝鲜半岛的的胜利。除了消灭匈奴、大宛等西域国家、朝鲜半岛的军事胜利，两汉对外军事斗争就没有太多的闪光点。&lt;/p&gt;   &lt;p&gt;    &lt;img src="https://iknow-pic.cdn.bcebos.com/09fa513d269759ee97452cb4a2fb43166d22dfbc?x-bce-process=image/resize,m_lfit,w_450,h_600,limit_1"&gt;&lt;/img&gt;&lt;/p&gt;   &lt;p&gt;上图_ 西汉与匈奴的战争&lt;/p&gt;   &lt;p&gt;两汉军事斗争主打匈奴，唐朝的对外军事斗争可是四面开花，全面结果。公元629年到630年，唐军10万大军在李靖、李勣等人的带领下奋勇出击，经过一年作战就消灭东突厥汗国，俘虏颉利可汗。紧接着，唐太宗时期的唐军又拿下了吐谷浑、薛延陀、攻克高昌汗国，重创高句丽。由于李世民时期的对外作战连战连胜，周围各国都将李世民称作“天可汗”，其含义是“全天下共同的君主”。唐朝大臣王玄策还在唐太宗后期率领吐蕃等国组成的联军进攻印度，俘虏印度国王。&lt;/p&gt;   &lt;p&gt;到了唐高宗时期，唐朝强大的军力进一步体现出来。李治在位时期的唐军消灭了西突厥汗国、百济和高句丽。公元663年，刘瑞轨的唐军水师在白江口歼灭日军，日本对唐朝彻底服气。经过唐太宗和唐高宗两代帝王的励精图治，唐朝确立了亚洲地区的绝对军事霸权。西汉时期有“犯我强汉者，虽远必诛”。盛唐时期的军力依然是“犯我大唐者，虽远必诛”。&lt;/p&gt;   &lt;p&gt;    &lt;img src="https://iknow-pic.cdn.bcebos.com/21a4462309f790522c0a0b411cf3d7ca7bcbd5bc?x-bce-process=image/resize,m_lfit,w_450,h_600,limit_1"&gt;&lt;/img&gt;&lt;/p&gt;   &lt;p&gt;上图_ 唐征高句丽百济之战&lt;/p&gt;   &lt;p&gt;没有强大的军力作为依托，没有唐军将士一次次取得的对外胜利，唐朝的影响力是很难波及到世界各国。&lt;/p&gt;   &lt;p&gt;等到安史之乱爆发后，周边国家派出军队和唐军并肩作战。比如，在安史之乱的转折之战——香积寺之战中，除了唐军的西域军团和叛军交锋，唐军阵营中还有“国际联军”：骨力裴罗可汗的4000回鹘军队，曼苏尔哈里发的4000阿拉伯军团，以及南诏、于阗等小国派出的军队和唐军并肩作战。“国际联军”帮助我国中央政府平定叛乱，这在中国历史上是极其少见的一件事情。唐朝的国际影响力从这一点也能体现出来。&lt;/p&gt;   &lt;p&gt;    &lt;img src="https://iknow-pic.cdn.bcebos.com/4e4a20a4462309f7237ae16d620e0cf3d7cad6bc?x-bce-process=image/resize,m_lfit,w_450,h_600,limit_1"&gt;&lt;/img&gt;&lt;/p&gt;   &lt;p&gt;上图_ 安史之乱&lt;/p&gt;   &lt;p&gt;正因为以上三点原因，让唐朝成为了外国人眼中中国的“绝对象征”。以至于今天，外国人都把中国人称为“唐人”。所以正是“盛唐精神”让中国历史达到了前所未有的高度，也正是“盛世大唐”让世界各国对中国历史和文化刮目相看。&lt;/p&gt;   &lt;p&gt;作者：军事帅哥校正/编辑：莉莉丝&lt;/p&gt;   &lt;p&gt;参考资料：《汉书》《后汉书》《旧唐书》《新唐书》《盛唐气象》&lt;/p&gt;   &lt;p&gt;文字由历史大学堂团队创作，配图源于网络版权归原作者所有&lt;/p&gt;   &lt;p&gt;    &lt;br /&gt;&lt;/p&gt;
   &lt;div&gt;
    &lt;p&gt;特别声明&lt;/p&gt;
    &lt;p&gt;
本文为自媒体、作者等在百度知道日报上传并发布，仅代表作者观点，不代表百度知道日报的观点或立场，知道日报仅提供信息发布平台。合作及供稿请联系zdribao@baidu.com。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
  &lt;div&gt;
   &lt;h2&gt;为您推荐：&lt;/h2&gt;


&lt;/div&gt;
  &lt;div&gt;
   &lt;div&gt;
+1

点个赞吧

赞(    &lt;em&gt;0&lt;/em&gt;)
&lt;/div&gt;
&lt;/div&gt;
  &lt;div&gt;
   &lt;h3&gt;关注作者&lt;/h3&gt;
   &lt;div&gt;
    &lt;a href="https://zhidao.baidu.com/daily/author?un=%E5%8E%86%E5%8F%B2%E5%A4%A7%E5%AD%A6%E5%A0%82&amp;ie=gbk" target="_blank"&gt;
     &lt;img alt="&amp;#22836;&amp;#20687;" height="80" src="https://iknow-pic.cdn.bcebos.com/b64543a98226cffc6a44f5f6b4014a90f603ea0c?x-bce-process=image/quality,q_85" width="80"&gt;&lt;/img&gt;
&lt;/a&gt;


    &lt;a href="https://zhidao.baidu.com/daily/author?un=%E5%8E%86%E5%8F%B2%E5%A4%A7%E5%AD%A6%E5%A0%82&amp;ie=gbk" target="_blank"&gt;历史大学堂&lt;/a&gt;

“历史堂”原创出品，品读书史

    &lt;a href="https://zhidao.baidu.com//daily/undefined"&gt;
     &lt;strong&gt;&lt;/strong&gt;
关注&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/61176-%E6%B1%89%E6%9C%9D-%E7%9B%9B%E4%B8%96-%E6%AF%94%E5%94%90</guid>
      <pubDate>Fri, 15 Jan 2021 06:02:41 CST</pubDate>
    </item>
    <item>
      <title>踩坑记：Flink 事件时间语义下数据乱序丢数</title>
      <link>https://itindex.net/detail/60891-flink-%E4%BA%8B%E4%BB%B6-%E6%97%B6%E9%97%B4</link>
      <description>&lt;blockquote&gt;❝  &lt;p&gt;本文详细介绍了在上游使用处理时间语义的 flink 任务出现故障后，重启消费大量积压在上游的数据并产出至下游数据乱序特别严重时，下游 flink 任务使用事件时间语义时遇到的大量丢数问题以及相关的解决方案。&lt;/p&gt;❞&lt;/blockquote&gt; &lt;p&gt;本文分为以下几个部分：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;strong&gt;「1.本次踩坑的应用场景」&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;「2.应用场景中发生的丢数故障分析」&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;「3.待修复的故障点」&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;「4.丢数故障解决方案及原理」&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;「5.总结」&lt;/strong&gt;&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;应用场景&lt;/h2&gt; &lt;p&gt;应用场景如下：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;strong&gt;「flink 任务 A」&lt;/strong&gt;以   &lt;strong&gt;「处理时间」&lt;/strong&gt;语义做过滤产出新增 xx 明细数据至   &lt;strong&gt;「Kafka Y」&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;「flink 任务 B」&lt;/strong&gt;以   &lt;strong&gt;「事件时间」&lt;/strong&gt;语义消费   &lt;strong&gt;「Kafka Y」&lt;/strong&gt;做窗口聚合操作产出分钟级别聚合指标至   &lt;strong&gt;「Kafka Z」&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;「Kafka Z」&lt;/strong&gt;实时导入至   &lt;strong&gt;「Druid」&lt;/strong&gt;以做即时 OLAP 分析，并且展示在 BI 应用看板   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;img&gt;&lt;/img&gt;&lt;/p&gt; &lt;h2&gt;丢数故障分析&lt;/h2&gt; &lt;p&gt;简要介绍下这次生产中故障场景。整条故障追踪链路如下：&lt;/p&gt; &lt;p&gt;故障一：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;收到报警反馈   &lt;strong&gt;「flink 任务 A」&lt;/strong&gt;入口流量为 0&lt;/li&gt;  &lt;li&gt;定位   &lt;strong&gt;「flink 任务 A」&lt;/strong&gt;中某个算子的故障导致整个 job 卡住&lt;/li&gt;  &lt;li&gt;导致此   &lt;strong&gt;「flink 任务 A」&lt;/strong&gt;上游   &lt;strong&gt;「kafka X」&lt;/strong&gt;积压了大量数据&lt;/li&gt;  &lt;li&gt;重启   &lt;strong&gt;「flink 任务 A」&lt;/strong&gt;后，消费大量积压在上游   &lt;strong&gt;「kafka X」&lt;/strong&gt;数据完成，任务恢复正常&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;故障一从而引发下游的故障二：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;由于   &lt;strong&gt;「flink 任务 A」&lt;/strong&gt;使用了   &lt;strong&gt;「处理时间」&lt;/strong&gt;语义处理数据，并且有过滤和 keyBy 分桶窗口逻辑，在重启后消费大量积压在上游的数据时，导致 sink rebalance 后产出到下游   &lt;strong&gt;「kafka Y」&lt;/strong&gt;各个分区数据中的 server_timestamp 是乱序的&lt;/li&gt;  &lt;li&gt;下游   &lt;strong&gt;「flink 任务 B」&lt;/strong&gt;在消费   &lt;strong&gt;「Kafka Y」&lt;/strong&gt;时使用了   &lt;strong&gt;「事件时间」&lt;/strong&gt;语义处理数据，并且使用了数据中的 server_timestamp 作为   &lt;strong&gt;「事件时间」&lt;/strong&gt;时间戳&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;「flink 任务 B」&lt;/strong&gt;消费了乱序很严重的数据之后，导致在窗口聚合计算时丢失了大量数据&lt;/li&gt;  &lt;li&gt;最终展示在 BI 应用中的报表有丢失数据的情况   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;img&gt;&lt;/img&gt;&lt;/p&gt; &lt;h2&gt;待修复的故障点&lt;/h2&gt; &lt;ul&gt;  &lt;li&gt;1.   &lt;strong&gt;「flink 任务 A」&lt;/strong&gt;的稳定性故障，这部分解决方案暂不在本文中介绍&lt;/li&gt;  &lt;li&gt;2.   &lt;strong&gt;「flink 任务 B」&lt;/strong&gt;消费上游乱序丢数故障，解决方案在下文介绍&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;解决方案以及原理&lt;/h2&gt; &lt;h3&gt;丢数故障解决方案&lt;/h3&gt; &lt;p&gt;解决方案是以下游  &lt;strong&gt;「flink 任务 B」&lt;/strong&gt;作为切入点，直接给出  &lt;strong&gt;「flink 任务 B」&lt;/strong&gt;的 sql 代码解决方案，java code 也可以按照这个方案实现，其本质原理相同。下文进行原理解释。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;SELECT   &lt;br /&gt;  to_unix_timestamp(server_timestamp / bucket) AS timestamp, -- format 成原有的事件时间戳   &lt;br /&gt;  count(id) as id_cnt,   &lt;br /&gt;  sum(duration) as duration_sum   &lt;br /&gt;FROM   &lt;br /&gt;  source_table   &lt;br /&gt;GROUP BY   &lt;br /&gt;  TUMBLE(proctime, INTERVAL &amp;apos;1&amp;apos; MINUTE),   &lt;br /&gt;  server_timestamp / bucket -- 根据事件时间分桶计算，将相同范围（比如 1 分钟）事件时间的数据分到一个桶内   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;解决方案原理&lt;/h3&gt; &lt;p&gt;首先明确一个无法避免的问题，在不考虑 watermark 允许延迟设置特别大的情况下，只要上游使用到了处理时间语义，下游使用事件时间语义，一旦上游发生故障重启并在短时间内消费大量数据，就不可避免的会出现上述错误以及故障。&lt;/p&gt; &lt;p&gt;在下游消费方仍然需要将对应事件时间戳的数据展示在 BI 平台报表中、并且全链路时间语义都为处理时间保障不丢数的前提下。解决方案就是在聚合并最终产出对应事件时间戳的数据。&lt;/p&gt; &lt;p&gt;最后的方案如下：整条链路全部为处理时间语义，窗口计算也使用处理时间，但是产出数据中的时间戳全部为事件时间戳。在出现故障的场景下，一分钟的窗口内的数据的事件时间戳可能相差几个小时，但在最终窗口聚合时可以根据事件时间戳划分到对应的事件时间窗口内，下游 BI 应用展示时使用此事件时间戳即可。&lt;/p&gt; &lt;blockquote&gt;❝  &lt;p&gt;注意：sql 中的 bucket 需要根据具体使用场景进行设置，如果设置过于小，比如非故障场景下按照处理时间开 1 分钟的窗口，bucket
设为 60000（1 分钟），那么极有可能，这个时间窗口中所有数据的 server_timestamp 都集中在某两分钟内，那么这些数据就会被分到两个桶（bucket）内，则会导致严重的数据倾斜。&lt;/p&gt;❞&lt;/blockquote&gt; &lt;h3&gt;输入数据样例&lt;/h3&gt; &lt;p&gt;模拟上述故障，  &lt;strong&gt;「flink B」&lt;/strong&gt;的任务某一个窗口内的数据输入如下。&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;th&gt;server_timestamp&lt;/th&gt;   &lt;th&gt;id&lt;/th&gt;   &lt;th&gt;duration&lt;/th&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;2020/9/01 21:14:38&lt;/td&gt;   &lt;td&gt;1&lt;/td&gt;   &lt;td&gt;300&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;2020/9/01 21:14:50&lt;/td&gt;   &lt;td&gt;1&lt;/td&gt;   &lt;td&gt;500&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;2020/9/01 21:25:38&lt;/td&gt;   &lt;td&gt;2&lt;/td&gt;   &lt;td&gt;600&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;2020/9/01 21:25:38&lt;/td&gt;   &lt;td&gt;3&lt;/td&gt;   &lt;td&gt;900&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;2020/9/01 21:25:38&lt;/td&gt;   &lt;td&gt;2&lt;/td&gt;   &lt;td&gt;800&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h3&gt;输出数据样例&lt;/h3&gt; &lt;p&gt;按照上述解决方案中的 sql 处理过后，输出数据如下，则可以解决此类型丢数故障。&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;th&gt;timestamp&lt;/th&gt;   &lt;th&gt;id_cnt&lt;/th&gt;   &lt;th&gt;duration_sum&lt;/th&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;2020/9/01 21:14:00&lt;/td&gt;   &lt;td&gt;2&lt;/td&gt;   &lt;td&gt;900&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;2020/9/01 21:25:00&lt;/td&gt;   &lt;td&gt;3&lt;/td&gt;   &lt;td&gt;2300&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h2&gt;总结&lt;/h2&gt; &lt;p&gt;本文分析了在 flink 应用中：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;strong&gt;「上游使用处理时间语义的 flink 任务出现故障、重启消费大量积压数据并产出至下游数据乱序特别严重时，下游使用事件时间语义时遇到的大量丢数问题」&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;「以整条链路为处理时间语义的前提下，产出的数据时间戳为事件时间戳解决上述问题」&lt;/strong&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;「以 sql 代码给出了丢数故障解决方案样例」&lt;/strong&gt;&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;学习资料&lt;/h2&gt; &lt;h3&gt;flink&lt;/h3&gt; &lt;ul&gt;  &lt;li&gt;https://github.com/flink-china/flink-training-course/blob/master/README.md&lt;/li&gt;  &lt;li&gt;https://ververica.cn/developers-resources/&lt;/li&gt;  &lt;li&gt;https://space.bilibili.com/33807709   &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;img&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>dev</category>
      <guid isPermaLink="true">https://itindex.net/detail/60891-flink-%E4%BA%8B%E4%BB%B6-%E6%97%B6%E9%97%B4</guid>
      <pubDate>Wed, 23 Sep 2020 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>ORACLE----TimeStamp(时间戳)字段自动更新</title>
      <link>https://itindex.net/detail/60707-oracle-timestamp-%E6%97%B6%E9%97%B4%E6%88%B3</link>
      <description>&lt;div&gt;  &lt;div&gt;之前对id的自增长进行了设置，原以为时间戳的更新也类似，使用序列之类的，但最终发现跟序列没半毛钱关系。触发器是肯定要用的，但编写脚本也是需要的。&lt;/div&gt;  &lt;div&gt;   &lt;br /&gt;&lt;/div&gt;  &lt;div&gt;（1）创建触发器&lt;/div&gt;  &lt;div&gt;   &lt;br /&gt;&lt;/div&gt;  &lt;div&gt;   &lt;br /&gt;&lt;/div&gt;  &lt;div&gt;   &lt;br /&gt;&lt;/div&gt;  &lt;div&gt;   &lt;br /&gt;&lt;/div&gt;  &lt;div&gt;图2中，我们要选择事件（即例子中的INSERT,UPDATE），触发时间（即INSERT,UPDATE事件的前或后，例子中是before），列（例子中为MODIFIED_DATE，为TimeStamp类型），新旧值（为系统默认值OLD和NEW）。&lt;/div&gt;  &lt;div&gt;   &lt;br /&gt;&lt;/div&gt;  &lt;div&gt;（2）添加脚本&lt;/div&gt;  &lt;div&gt;点击上图的确定后，将生成脚本&lt;/div&gt;  &lt;div&gt;   &lt;br /&gt;&lt;/div&gt;  &lt;div&gt;CREATE OR REPLACE TRIGGER TRIGGER3 &lt;/div&gt;  &lt;div&gt;BEFORE INSERT OR UPDATE OF MODIFIED_DATE ON T_MATERIAL_STOCK_CURRENT &lt;/div&gt;  &lt;div&gt;REFERENCING OLD AS OLD NEW AS NEW &lt;/div&gt;  &lt;div&gt;BEGIN&lt;/div&gt;  &lt;div&gt;  NULL;&lt;/div&gt;  &lt;div&gt;END;&lt;/div&gt;  &lt;div&gt;   &lt;br /&gt;&lt;/div&gt;  &lt;div&gt;我们需要对BEGIN和END之间的代码进行更新。即将NULL;替换成&lt;/div&gt;  &lt;div&gt;   &lt;br /&gt;&lt;/div&gt;  &lt;div&gt;:NEW.MODIFIED_DATE := SYSDATE;&lt;/div&gt;  &lt;div&gt;   &lt;br /&gt;&lt;/div&gt;  &lt;div&gt;其中MODIFIED_DATE 为列名称。SYSDATE为系统时间戳。&lt;/div&gt;  &lt;div&gt;   &lt;br /&gt;&lt;/div&gt;  &lt;div&gt;（3）启动触发器&lt;/div&gt;  &lt;div&gt;与设置id自增长类似。见前文。&lt;/div&gt;  &lt;div&gt;   &lt;br /&gt;&lt;/div&gt;  &lt;div&gt;至此，TimeStamp可自动更新。&lt;/div&gt;  &lt;div&gt;   &lt;br /&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/60707-oracle-timestamp-%E6%97%B6%E9%97%B4%E6%88%B3</guid>
      <pubDate>Mon, 22 Jun 2020 17:38:09 CST</pubDate>
    </item>
    <item>
      <title>MySQL/Oracle：让表的时间字段在insert和update时自动更新_xuejianbest的专栏-CSDN博客_updated =&gt; time 怎麼插入時間</title>
      <link>https://itindex.net/detail/60701-mysql-oracle-%E6%97%B6%E9%97%B4</link>
      <description>&lt;div&gt;让字段自动更新为当前时间戳：&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;--insert时若不指定updated值，则插入当前时间&lt;/div&gt; &lt;div&gt;CREATE TABLE `test_update` (&lt;/div&gt; &lt;div&gt;  `id` int(32) NOT NULL,&lt;/div&gt; &lt;div&gt;  `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,  &lt;/div&gt; &lt;div&gt;  PRIMARY KEY (`id`)&lt;/div&gt; &lt;div&gt;) ENGINE=InnoDB DEFAULT CHARSET=utf8;&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;--insert时和update时若不指定updated值，则插入当前时间&lt;/div&gt; &lt;div&gt;CREATE TABLE `test_update` (&lt;/div&gt; &lt;div&gt;  `id` int(32) NOT NULL,&lt;/div&gt; &lt;div&gt;  `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, &lt;/div&gt; &lt;div&gt;  PRIMARY KEY (`id`)&lt;/div&gt; &lt;div&gt;) ENGINE=InnoDB DEFAULT CHARSET=utf8;&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;--insert时若不指定updated值，则为NULL；update时若不指定updated值，则插入当前时间&lt;/div&gt; &lt;div&gt;CREATE TABLE `test_update` (&lt;/div&gt; &lt;div&gt;  `id` int(32) NOT NULL,&lt;/div&gt; &lt;div&gt;  `updated` timestamp NULL ON UPDATE CURRENT_TIMESTAMP, &lt;/div&gt; &lt;div&gt;  PRIMARY KEY (`id`)&lt;/div&gt; &lt;div&gt;) ENGINE=InnoDB DEFAULT CHARSET=utf8;&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;timestamp类型只用来存储创建时间和更新时间，因为可以自动更新，不需要程序里来处理。&lt;/div&gt; &lt;div&gt;一般的时间用varchar存储比较好处理。&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;添加一个新字段：&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;ALTER TABLE `toll_station` ADD COLUMN &lt;/div&gt; &lt;div&gt;  `update_time` timestamp NOT NULL &lt;/div&gt; &lt;div&gt;  DEFAULT CURRENT_TIMESTAMP &lt;/div&gt; &lt;div&gt;  ON UPDATE CURRENT_TIMESTAMP &lt;/div&gt; &lt;div&gt;  COMMENT &amp;apos;记录数据更新时间，自动更新，不要手动修改&amp;apos; &lt;/div&gt; &lt;div&gt;AFTER `status`;&lt;/div&gt; &lt;div&gt;————————————————&lt;/div&gt; &lt;div&gt;  &lt;br /&gt;&lt;/div&gt; &lt;div&gt;  &lt;h2&gt;   &lt;a href="https://www.cnblogs.com/xuanyuanvista/archive/2009/08/25/ora_rowscn.html"&gt;用sql语句查看oracle表每条记录的创建时间(ora_rowscn)&lt;/a&gt;&lt;/h2&gt;  &lt;div&gt;   &lt;p&gt;      有时要查看表中的那条记录是什么时候创建的，有的会去用logminer去查看，有的会用闪回查询，其实很简单，不用那么麻烦，oracle的建每张表时都会建一个隐含的字段ora_rowscn，看到这个字段的名字，有写人会想到scn？对了，oracle在向表中插入记录时会记录插入记录时当前数据库的scn，那么聪明的你很快就会想到，既然有每条记录的scn，那么通过scn不就能查到每条记录的创建时间啦？全对!    &lt;br /&gt;      只要一句话，就可以轻松查看表记录的生成时间，相信这个应该会有帮助的!    &lt;br /&gt;      举个例子:    &lt;br /&gt;      select to_char(scn_to_timestamp(ORA_ROWSCN),&amp;apos;yyyy-mm-dd hh24:mi:ss&amp;apos;) insert_time from scott.emp;    &lt;br /&gt;      结果将会看到emp表中每条记录创建时的时间。&lt;/p&gt;   &lt;p&gt;      默认情况下，每行记录的ORA_ROWSCN是基于Block的，这样是不准确的，除非在建表的时候执行开启行级跟踪(create table … rowdependencies)，这样就会是在行级记录scn。    &lt;br /&gt;      所以要想查看准确的记录创建时间则在建表时要加rowdependencies选项！&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/60701-mysql-oracle-%E6%97%B6%E9%97%B4</guid>
      <pubDate>Sun, 21 Jun 2020 19:31:49 CST</pubDate>
    </item>
    <item>
      <title>如何正确理解 RT 并监控 MySQL 的响应时间</title>
      <link>https://itindex.net/detail/60670-%E6%AD%A3%E7%A1%AE-%E7%90%86%E8%A7%A3-rt</link>
      <description>&lt;h2&gt;一、前言&lt;/h2&gt;
 &lt;p&gt;响应时间（response time 简称 RT）是从系统接收请求开始到返回响应之间的时间跨度，是一项极其重要的性能指标。它可以从侧面反映系统的整体吞吐量，也是业务请求（比如 sql 请求）的性能好坏的判断依据。&lt;/p&gt;
 &lt;p&gt;举个例子 A 要从杭州坐飞机到北京机场，经历如下：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;从公司到萧山机场 40min
机场安检，候机，登机 40min
飞机飞行 耗时 100min
飞机落地，打的到望京 耗时40min
RT= 40 + 40 + 100 + 40 =220min
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;其中真正的 ‘执行’ 时间就飞机飞行的时间（100min+40+40），其他安检、候机、堵车的都是等待时间。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;RT = 等待时间 + 执行时间&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;假如到机场的过程中发生堵车，或者空中管制导致候机时间延长，整体的 RT 也会变长，但是飞机飞行时间是相对一定的。&lt;/p&gt;
 &lt;p&gt;从技术的角度来看 SQL 的请求路径：&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;app &amp;lt;----&amp;gt;（网络建立连接，data 传输）&amp;lt;----&amp;gt; proxy &amp;lt;----&amp;gt;（网络建立连接，data 传输）&amp;lt;----&amp;gt; mysql（执行）&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;因为网络问题丢包，重传等导致数据传输时间增加，进而导致总体的 RT 时间增加 ，还有常见的案例 app 服务器 cpu 飙高导致程序执行的速度变慢，JAVA 程序 GC 等因素也会导致 RT 升高。所以说 SQL 慢，其实 RT 就会高。但是反过来   &lt;strong&gt;RT 高，不一定是 SQL 慢的原因&lt;/strong&gt; 。如果是开发同学遇到监控尤其是 trace 系统发现某个接口慢了，并不一定是 SQL 慢。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;重点：    &lt;strong&gt;不要把 trace 系统中的监控 rt 直接当做 db 的执行时间&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;参考案例：  &lt;a href="https://mp.weixin.qq.com/s?__biz=MzI4NjExMDA4NQ==&amp;mid=2648450714&amp;idx=1&amp;sn=d2a25d111c78147001aa9329dbbf9dbd&amp;scene=21#wechat_redirect"&gt;Strace 解决性能问题案例一则&lt;/a&gt;&lt;/p&gt;
 &lt;h2&gt;二、如何监控&lt;/h2&gt;
 &lt;p&gt;前面说了 RT 的定义以及它所代表意义。接下来我们看看如何监控数据库的 RT ，现有的方式主要有两种。&lt;/p&gt;
 &lt;h3&gt;2.1 tcprstat&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;tcprstat&lt;/strong&gt; 是 Percona 基于 libpcap 研发的工具，是通过测量 TCP 的 request 和 response 所需的时间间隔，适用于一问一答式协议类型的处理。通常用来监测 MySQL 响应时间，或者说是请求在服务器端的处理时间，其输出结果包括了响应时间相关的统计值，用来诊断服务器端性能状况。&lt;/p&gt;
 &lt;p&gt;举个例子：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://static001.infoq.cn/resource/image/e3/22/e3c3ebe119614db27fc48fb335e94322.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;其输出结果包括了时间戳，以及响应时间的最大值、均值、方差等信息，输出信息可以通过 -f 参数进行定制，其中响应时间的单位为微妙。&lt;/p&gt;
 &lt;p&gt;其中对我们比较重要的是：&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;count：此间隔内处理完成的请求数量。   &lt;br /&gt;
avg：此间隔内所有完成的请求，响应的平均时间。   &lt;br /&gt;
   &lt;code&gt;95_avg&lt;/code&gt;：此间隔内，95% 的请求量的平均响应时间，单位微妙，该值较能体现 MySQL Server 的查询平均响应时间。   &lt;br /&gt;
如果我们只需要输出 count, 平均时间，    &lt;code&gt;95_avg&lt;/code&gt;,    &lt;code&gt;99_avg&lt;/code&gt; 则可以用如下命令。tcprstat -p 3312 -t 1 -n 0 -l ip_address -f ‘%T\t%n\t%a\t%95a\t%99a\n’&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://static001.infoq.cn/resource/image/59/26/59e651e558abefb7eeb3b28c2d7ff026.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;关于 -f 的参数解释如下，读者朋友可以根据需要来调整输出&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://static001.infoq.cn/resource/image/36/8a/360e6038da0016808723cc4bb40e2b8a.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;如果执行 tcprstat 遇到如下问题：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;# tcprstat -p 3312 -t 1 -n 5
pcap: SIOCGIFFLAGS: bonding_masters: No such device
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;可以通过指定本地   &lt;strong&gt;ip -l local_ip&lt;/strong&gt; 来解决。&lt;/p&gt;
 &lt;h3&gt;2.2 MySQL 插件&lt;/h3&gt;
 &lt;p&gt;Percona Server 提供一个叫做响应时间区间的功能，将 sql 耗时在指定区间的请求次数和总共的执行时间记录到表里面。其中时间区间跨度由   &lt;code&gt;query_response_time_range_base&lt;/code&gt; 控制。&lt;/p&gt;
 &lt;p&gt;常用的区间范围为：(0, 0.000001], (0.000001, 0.000010],(0.000010,0.000100],(0.000100,0.001000],(0.001000, 0.010000], (0.010000,0.100000],(0.100000,1.000000],(1,10] 。&lt;/p&gt;
 &lt;p&gt;从 MySQL 5.6 开始以插件形式安装：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;INSTALL PLUGIN QUERY_RESPONSE_TIME_AUDIT SONAME &amp;apos;query_response_time.so&amp;apos;;
INSTALL PLUGIN QUERY_RESPONSE_TIME SONAME &amp;apos;query_response_time.so&amp;apos;;
INSTALL PLUGIN QUERY_RESPONSE_TIME_READ SONAME &amp;apos;query_response_time.so&amp;apos;;
INSTALL PLUGIN QUERY_RESPONSE_TIME_WRITE SONAME &amp;apos;query_response_time.so&amp;apos;;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;然后通过 show plugins 命令检查插件是否安装成功。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;&amp;gt; SHOW PLUGINS;
......
| QUERY_RESPONSE_TIME     | ACTIVE  | INFORMATION SCHEMA | query_response_time.so | GPL   |
| QUERY_RESPONSE_TIME_AUDIT  | ACTIVE  | AUDIT       | query_response_time.so | GPL   |
| QUERY_RESPONSE_TIME_READ  | ACTIVE  | INFORMATION SCHEMA | query_response_time.so | GPL   |
| QUERY_RESPONSE_TIME_WRITE  | ACTIVE  | INFORMATION SCHEMA | query_response_time.so | GPL   |
+-----------------------------+----------+--------------------+------------------------+---------
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;安装完成之后 在   &lt;code&gt;INFORMATION_SCHEMA&lt;/code&gt; 生成三张表&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;QUERY_RESPONSE_TIME_WRITE 记录所有写请求的响应时间分布
QUERY_RESPONSE_TIME_READ 记录所有读请求的响应时间分布
QUERY_RESPONSE_TIME 可以认为是所有请求的响应时间分布。
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;查看 QUERY_RESPONSE_TIME 的内容&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://static001.infoq.cn/resource/image/26/7d/26e9473129a029220f5eb93535c85c7d.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;查询结果中 717 个 sql 请求耗时在 (0, 0.000001] 之间。47898 个 sql 请求的耗时在 (0.000001, 0.000010]，总耗时 0.29 秒，其他以此类推。   &lt;strong&gt;需要注意的是 count 和total是累计值，监控的时候需要取后值减前值除以采样的时间间隔。&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;如何开启响应时间统计&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;在命令行中执行&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;SET GLOBAL query_response_time_stats = 1 ;&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;在 my.cnf 中&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;query_response_time_stats = 1&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;重置（将数据清零）三张表的统计值&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;SET GLOBAL query_response_time_flush=‘ON’;&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;常用的 sql&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;INFORMATION_SCHEMA [RW][TEST:qa_single_0:3312] 11:50:44
&amp;gt;SELECT c.count, c.time, (SELECT SUM(a.count) FROM INFORMATION_SCHEMA.QUERY_RESPONSE_TIME as a WHERE a.count != 0) as query_count, (SELECT COUNT(*)     FROM INFORMATION_SCHEMA.QUERY_RESPONSE_TIME as b WHERE b.count != 0) as not_zero_region_count, (SELECT COUNT(*)     FROM INFORMATION_SCHEMA.QUERY_RESPONSE_TIME) as region_count FROM INFORMATION_SCHEMA.QUERY_RESPONSE_TIME as c WHERE c.count &amp;gt; 0;
+-------+----------------+-------------+-----------------------+--------------+
| count | time           | query_count | not_zero_region_count | region_count |
+-------+----------------+-------------+-----------------------+--------------+
|     1 |       0.000001 |       71370 |                     7 |           14 |
|    86 |       0.000010 |       71370 |                     7 |           14 |
| 47375 |       0.000100 |       71370 |                     7 |           14 |
| 23404 |       0.001000 |       71370 |                     7 |           14 |
|   423 |       0.010000 |       71370 |                     7 |           14 |
|    79 |       0.100000 |       71370 |                     7 |           14 |
|     2 |       1.000000 |       71370 |                     7 |           14 |
+-------+----------------+-------------+-----------------------+--------------+
7 rows in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;通过监控脚本获取响应时间的数据在 grafna 展示的结果如下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://static001.infoq.cn/resource/image/3d/07/3d390051455021a55306d695df02df07.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://static001.infoq.cn/resource/image/53/9d/53d059434adaf864a2c3d6ef72ce779d.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;其他更详细的介绍可以去查阅 Percona 的官方文档。&lt;/p&gt;
 &lt;h2&gt;三、小结&lt;/h2&gt;
 &lt;p&gt;本文总结介绍 RT 在技术体系中的含义，以及介绍两种监控 MySQL 响应时间的方法。如果有其他更好的方式方法，欢迎读者朋友一起讨论。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;本文转载自公众号有赞coder（ID：youzan_coder）。&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;原文链接&lt;/strong&gt;：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://mp.weixin.qq.com/s?__biz=MzAxOTY5MDMxNA==&amp;mid=2455760851&amp;idx=1&amp;sn=9b3e3fc9e294864967b16ccfbf81abf4&amp;chksm=8c6869f6bb1fe0e0551835aa299c90481e1c14fa0cf9d512044e01e358b737058cb1d075962a&amp;scene=27#wechat_redirect"&gt;https://mp.weixin.qq.com/s?__biz=MzAxOTY5MDMxNA==&amp;amp;mid=2455760851&amp;amp;idx=1&amp;amp;sn=9b3e3fc9e294864967b16ccfbf81abf4&amp;amp;chksm=8c6869f6bb1fe0e0551835aa299c90481e1c14fa0cf9d512044e01e358b737058cb1d075962a&amp;amp;scene=27#wechat_redirect&lt;/a&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/60670-%E6%AD%A3%E7%A1%AE-%E7%90%86%E8%A7%A3-rt</guid>
      <pubDate>Sat, 13 Jun 2020 14:05:00 CST</pubDate>
    </item>
    <item>
      <title>履约时间预估：如何让外卖更快送达？</title>
      <link>https://itindex.net/detail/60660-%E6%97%B6%E9%97%B4-%E5%A4%96%E5%8D%96-%E9%80%81%E8%BE%BE</link>
      <description>&lt;div&gt;  &lt;p&gt;   &lt;img src="https://img1.tuicool.com/BNzeAre.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;小叽导读&lt;/strong&gt;：相信大部分人都有点过外卖的经历。在饿了么，为了让骑手能够准时准点将外卖送到每位用户的手中，让每位用户吃上一口热乎饭，阿里本地生活智慧物流团队对外卖履约时间预估这一问题进行了深入研究，并给出了有效的解决方案，本文将为大家做一个简要介绍。&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;前言&lt;/p&gt;  &lt;p&gt;近日，阿里本地生活智慧物流团队的一篇论文——Order Fulfillment Cycle Time Estimation for On-Demand Food Delivery被KDD’2020 Applied Data Science Track接收为Oral presentation（ACM Knowledge Discovery and Data Mining（SIGKDD），CCF A类会议，数据挖掘领域顶级会议，2020 年的口头报告接受率为 5.8%）。&lt;/p&gt;  &lt;p&gt;外卖履约时间预估（OFCT：Order Fulfillment Cycle Time）问题相比一般的时间预估问题而言更为复杂，其中存在餐厅与用户的供需关系、餐厅出餐时间的未知性以及骑手行为的不确定性等问题。在论文中我们向学术界首次详细介绍外卖履约时间预估这一问题，并给出了有效的解决方案，最后得到了审稿人的一致认可。&lt;/p&gt;  &lt;p&gt;通过逐步拆解整个外卖履约（履约：饿了么平台保障骑手能够将外卖准时送达给用户）的过程，我们分析了外卖履约时间预估相比常见的其他送达时间问题（例如打车）的显著差异，并针对影响履约时间长短的特征进行了解释和说明。对于用户而言可能只是看到外卖需要多久才能吃到，而在这背后需要我们提炼出丰富的影响因素，来保证履约时间预估的准确性。我们将这些影响因素输入深度神经网络来推断它们和履约时长的关系，同时我们进一步引入了餐厅、用户地址以及骑手的隐向量来增强模型的预测性能。最后，我们提出一个新颖的后处理神经网络算子，用于改善模型的收敛速度和准确度。我们所介绍的模型已在饿了么实际部署，每天服务于千万用户。&lt;/p&gt;  &lt;p&gt;背景介绍&lt;/p&gt;  &lt;p&gt;履约时间预估模型预估的是从用户下单到骑手将订单送达用户手上的这段时间（即预计送达时间）。饿了么平台每天产生千万级订单量，时间预估作为即时配送的其中一环，既影响用户体验同时也涉及到骑手履约，因此其准确性对平台而言至关重要，既不能预估的太长（影响用户体验），也不能预估的太短（骑手无法按时完成配送）。下图为时间预估涉及的各个环节。&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img1.tuicool.com/IjemeiN.png!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;主要环节包括：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;用户：用户从下单到订单送达其手中。对于每一位用户而言，肯定是希望能够准时拿到下单的餐品。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;餐厅：餐厅从接受订单到备餐完成。餐厅需要做到尽快完成备餐，这样才能够不影响骑手取餐及配送，如果骑手到达餐厅的时候需要等待很久的时间才能取走餐品，那么骑手容易焦虑，一部分用户也会在饿了么App上催促骑手。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;骑手：骑手从接收到订单到完成配送。其中包括骑手到达餐厅，然后从餐厅处取走订单对应的餐品。同时，骑手可能从餐厅处取多餐，因此需要等拿到所有订单骑手才会离开并进行配送。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;平台：饿了么平台需要从中协调用户、餐厅、骑手并兼顾配送效率。这其中包括订单指派与路径规划。订单指派是指将订单分给附近合适的骑手，而路径规划是指给骑手推荐合理的取送路径，此路径需要同时考虑骑手配送距离和订单超时风险。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;下图即为大家日常在饿了么上面点外卖的时候能够看到的信息，其中配送时间就是我们履约时间预估模型计算出的。&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img0.tuicool.com/QjI7fmM.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;外卖履约时间与 ETA 的差异&lt;/p&gt;  &lt;p&gt;Estimated Time of Arrival (ETA)即“预估送达时间”，一般预估的是从出发地到目的地的时间，打车场景中的预估送达时间即为一类典型的ETA问题。&lt;/p&gt;  &lt;p&gt;本文提出的外卖履约时间预估模型，预估的是从用户下单开始到骑手将餐品送达用户手中所花的时间，用户在饿了么点了外卖以后，订单在平台开始流转的过程如下图所示。&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img1.tuicool.com/7ZzURne.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;外卖履约时间预估相比预估送达时间而言更为特殊，主要体现在以下两方面：&lt;/p&gt;  &lt;p&gt;1  需要考虑更多影响因素&lt;/p&gt;  &lt;p&gt;一般的预估送达时间问题仅需考虑天气、交通状况，时空信息及路径信息等，而外卖履约时间预估问题除了考虑此类信息外，还需考虑餐厅的地理位置，餐厅订单备餐时间以及调度系统派单等信息。&lt;/p&gt;  &lt;p&gt;2  无法获取关键信息&lt;/p&gt;  &lt;p&gt;用户下单成功时饿了么已经为用户预估出预计送达时间，而此时订单并未被骑手接单，骑手需要被系统指派才能开始取餐和配送，因此我们无法提前获取骑手的信息及其实际的配送路径。&lt;/p&gt;  &lt;p&gt;以上两方面的差异给外卖履约时间预估问题的准确性带来极大的挑战。&lt;/p&gt;  &lt;p&gt;外卖履约时间预测一般需要哪些特征&lt;/p&gt;  &lt;p&gt;为了建模外卖履约时间，一般需要充分利用与订单信息相关的数据，具体包括：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;空间特征：包括大量id类特征，例如用户所在区域id，餐厅id，城市id及网格id等。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;时间特征：包括小时时刻，当天是否工作日等，如下图（a）。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;描述订单大小的特征：包括订单对应的菜品数量以及订单价格等。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;大家应该会好奇订单价格会对外卖时间长短造成什么影响？当用户下单的金额较高时通常餐品对应的重量或体积较大，比如用户预订了蛋糕或者集体点了很多杯奶茶，这种总金额高的订单对于骑手而言属于难配送订单，因此需要花费较长的履约时间。下图（b）展示出了这种相关性，可以看到订单价格的高低在一定程度上可以刻画出订单是否难配送的隐含信息。&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img1.tuicool.com/ZzyQnaj.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;供需关系对履约时长的影响&lt;/p&gt;  &lt;p&gt;从平台角度看，用户下单量和餐厅接单量不同时刻都在发生剧烈变化，这种供需维度上的变化对实际配送时长会造成极大影响。&lt;/p&gt;  &lt;p&gt;在介绍供需特征构造的工作前，先为大家介绍外卖配送中“波次”的概念：对于骑手身上的一组订单，对给定的一组订单取送顺序进行分组，保证每组中所有相关订单的取和送行为都在该组中，该分组则为骑手当前配送的波次。针对供需变化，我们构造了基于时段的供需比和完成率等特征。当供需比越高时波次的平均长度会变长，此时履约时间越长。&lt;/p&gt;  &lt;p&gt;另一方面当完成率越高时可以推断出骑手完成配送的订单越多，此时骑手可以继续承接系统接下来分派的订单。&lt;/p&gt;  &lt;p&gt;此外，我们通过餐厅当前待取餐单量（餐厅接单后等待骑手来取的订单数）来刻画餐厅的繁忙程度，当餐厅接单数变多而产能受限时会导致订单积压，此时如果骑手已经到达餐厅则需要花费较长的等待时间才能取到餐品，相应的当餐厅变繁忙时，模型预估的履约时间将变长。&lt;/p&gt;  &lt;p&gt;餐厅的出餐时间&lt;/p&gt;  &lt;p&gt;订单的出餐时间是外卖履约时间预估模型的一个重要影响因素，这个特征我们是通过聚合餐厅的历史出餐时间得到的。但目前存在的难点问题对出餐时间计算的准确性带来极大考验，主要包括：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;餐厅在备餐完成后缺乏人力来逐单点击出餐按钮，导致我们平台不能完全搜集到餐厅出餐的真实值，因此我们目前主要依靠系统采集的骑手点击出餐数据来标记餐厅的真实出餐时间。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;饿了么平台目前主要计算的是餐厅在饿了么App产生的订单，缺乏餐厅在其他渠道产生的订单或堂食订单数据，因此较难获取餐厅的实际供需情况。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;餐厅的真实出餐时间具有较大的随机性。例如餐厅针对某些餐品可能会提前进行备餐，这部分提前备好的餐品可以立即出餐。而对于用户下单时餐厅需要现做的餐品，骑手到达餐厅后可能需要等待一段时间才能取到餐，这部分现做的订单真实出餐时间将会偏长。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;订单的先后顺序不一定表示餐厅出餐的先后顺序。由于餐厅灶台数量有限，相应的灶台只会处理固定的菜品，因此在一批订单中如果出现相同的菜品，后厨会选择一起做，这种情况下部分订单的出餐时间会明显偏短。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;在实际运用时，我们是根据商家接单时间到骑手实际点击取餐时间来计算商户的真实出餐时间，而这其中存在一部分噪音数据：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;骑手接单后即刻点击到达餐厅&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;骑手接单后即刻点击取餐按钮&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;此外，对于一部分训练样本，我们认为骑手在取到餐品时实际上餐厅已经备餐完成，例如骑手晚取餐或骑手同时点击取多餐。针对这些数据我们在计算餐厅出餐时间特征时进行了一定比例的剔除。&lt;/p&gt;  &lt;p&gt;如何合理利用骑手信息&lt;/p&gt;  &lt;p&gt;饿了么从平台角度出发，将每个城市划分成了以“网格”为最小单元的不同区域，每个蜂鸟配送站点内的骑手会服务于站点周边范围内固定的若干个网格，骑手对站点辐射的网格内的商圈或者小区的熟悉程度决定了其配送效率。从下图大家可以看到，因为骑手对餐厅所在位置、用户所在小区都比较熟悉，因此在取餐或者配送的过程中并没有发生绕路的情况。&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img1.tuicool.com/6Fzmuyf.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;而用户下单成功时饿了么App会立刻为用户显示外卖预估履约时间，此时订单指派给具体哪位骑手来配送是未知的。为了充分利用与骑手相关的影响因素，我们根据骑手取餐距离、骑手当前接了多少订单等特征来表征订单可能被接单的每一位骑手，然后将可能接单的骑手序列进行特征编码传入外卖履约时间预估模型中，随后利用注意力机制提取骑手序列信息，以此来增强模型的预测能力。&lt;/p&gt;  &lt;p&gt;多维度相似订单的配送段 ETA&lt;/p&gt;  &lt;p&gt;配送段ETA指的是预估骑手到达目的地（用户所在位置）附近下车后将餐品送到用户手中所花的时间，是骑手配送的最终环节。&lt;/p&gt;  &lt;p&gt;为了估算配送段ETA，我们理论上可以直接采用回归模型来学习，但是常用的回归模型通常将输入转化为一系列的特征，并且通过有监督学习找到这些影响因素和输出目标之间的关系，为了方便学习和提高模型泛化能力通常基于神经网络和集成树模型将这些关系参数化为一个平滑的函数，但这种平滑假设的缺点是无法很好的处理长尾不规律case，可能会影响用户体验。例如当骑手送餐需要乘坐高层电梯时，如果遇上高峰期，可能需要等待很长的时间，而系统很难做到这种实时的预判。从下图可以看出，骑手送餐时在楼内花了7.6分钟。&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img0.tuicool.com/ERRVfuQ.png!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;为了部分缓解这种问题，我们借鉴了近期基于记忆的语言模型[1]的思想，将历史订单作为配送段时间预估的语料，通过构造多维特征来表征每个历史订单，当新的订单产生时我们基于K近邻来搜索出与新订单相似的若干个历史订单，然后对这若干个相似单的真实配送段时间做加权平均，以此作为新订单的预估配送段时间。最终我们将基于K近邻搜索出的预估配送段时间作为特征输入外卖履约时间预估模型中。&lt;/p&gt;  &lt;p&gt;针对长尾数据如何解决&lt;/p&gt;  &lt;p&gt;时间预估本质上属于回归问题，在训练模型的过程中我们发现模型收敛较慢且交叉验证的表现偏离预期，通过分析原因我们发现模型拟合的数据分布与真实履约时间的分布发生了偏移，真实的履约时间实际上是一个右偏长尾的分布，相当于有一小部分订单真实的配送时间偏长而模型没有学习到，针对此问题在本文中我们提出了一个新颖的后处理神经网络算子，针对外卖履约时间预估模型的拟合结果进行缩放和变换，用于改善模型的收敛速度和准确度。此后处理算子可描述为：&lt;/p&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;其中，&lt;/p&gt;  &lt;p&gt;：表示OFCT模型的拟合值。&lt;/p&gt;  &lt;p&gt;：表示样本真实值的均值。&lt;/p&gt;  &lt;p&gt;：表示样本真实值的标准差。&lt;/p&gt;  &lt;p&gt;：表示OFCT模型初始化均值。&lt;/p&gt;  &lt;p&gt;：表示OFCT模型初始化标准差。&lt;/p&gt;  &lt;p&gt;经过离线训练验证，后处理算子可有效提升外卖履约时间预估模型收敛速度和准确度。&lt;/p&gt;  &lt;p&gt;近期，我们又探索出一种新的后处理算子：自适应Box-Cox逆变换，相比于本文提出的后处理算子，自适应Box-Cox逆变换算子更加能够拟合长尾数据分布，使模型效果得到了进一步改善，目前此算子已经应用于线上推断。&lt;/p&gt;  &lt;p&gt;实验效果&lt;/p&gt;  &lt;p&gt;我们提出的外卖履约时间预估模型以“编码及预测”作为模型主体结构，具体结构如下图所示。&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img0.tuicool.com/qMRzqyI.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;其中，Nearest Courier Index这个模块实现了前面介绍的骑手信息编码，这个模块可以让模型充分利用可能接单的骑手信息；Postprocessing即为后处理算子，这个算子让模型能够更快收敛，同时也提升了模型的准确率。&lt;/p&gt;  &lt;p&gt;在我们提出外卖履约时间预估模型前，线上时间预估模型已经稳定运行了一段时间，因此我们以线上已有模型作为baseline进行了AB实验。下图实验数据显示，外卖履约时间预估模型相比于原有模型，MAE相对降低9.8%，用户投诉率相对降低19.3%，预测误差和用户投诉率均有明显改善，对用户体验产生了明显的正向效果。&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img0.tuicool.com/i2Ev22I.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;总结与展望&lt;/p&gt;  &lt;p&gt;在本文中我们为大家介绍了饿了么目前线上实际运行并服务于用户的外卖履约时间预估模型，通过离线评测及在线测试均证明了其有效性。&lt;/p&gt;  &lt;p&gt;未来我们主要将着力于如下两方面继续研究：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;出餐时间作为外卖履约时间预估模型的影响因素之一对其相当重要，但目前出餐时间处理相对简单因此具有相当大的提升空间。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;此外，我们将研发出一套通用的模型框架，即针对出餐时间和履约时间进行多任务学习。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;参考文献&lt;/p&gt;  &lt;p&gt;[1] Urvashi Khandelwal, Omer Levy, Dan Jurafsky, Luke Zettlemoyer, and Mike Lewis. Generalization through memorization: Nearest neighbor language models. In ICLR, 2020.&lt;/p&gt;  &lt;p&gt;深度学习课程&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;神经网络概览及算法详解&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;人工神经网络（Artificial Neural Networks，简写为 ANN）是一种模仿动物神经网络行为特征，进行分布式并行信息处理的算法数学模型。本课程共 36 课时，主要介绍神经网络学习规则、感知神经网络、竞争神经网络及反馈神经网络等，帮助大家掌握神经网络的相关概念和算法模型。&lt;/p&gt;  &lt;p&gt;识别下方二维码或点击文末“阅读原文”立刻学习吧：&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img1.tuicool.com/2QBZNrU.png!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;「 更多干货，更多收获 」&lt;/strong&gt;&lt;/p&gt;  &lt;h2&gt;   &lt;a href="http://mp.weixin.qq.com/s?__biz=MzU5ODUxNzEyNA==&amp;mid=2247485078&amp;idx=1&amp;sn=e3d82d3368013d0f6f6f84009d9e9bc0&amp;chksm=fe43b27dc9343b6b7ac9171653c800edcfb89ca39cf15b47ce32dab427bd7bd331efd8735018&amp;scene=21#wechat_redirect" rel="nofollow,noindex" target="_blank"&gt;   AliGraph：一个工业级的图神经网络平台&lt;/a&gt;&lt;/h2&gt;  &lt;h2&gt;   &lt;a href="http://mp.weixin.qq.com/s?__biz=MzU5ODUxNzEyNA==&amp;mid=2247484988&amp;idx=1&amp;sn=b9b1cb47d3ef226817faf746ef1efdc7&amp;chksm=fe43b2d7c9343bc167322cb26b7882ae4e5a823b0ff0b7b3c42a9caa1f1dcf39d501eb632018&amp;scene=21#wechat_redirect" rel="nofollow,noindex" target="_blank"&gt;   1亿人次玩嗨，灵魂画手如何赚红包？&lt;/a&gt;&lt;/h2&gt;  &lt;p&gt;   &lt;img src="https://img2.tuicool.com/bAb2uqn.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;关注机器智能&lt;/strong&gt;   &lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;    &lt;strong&gt;把握未来可能&lt;/strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;戳我，去学习。&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>tuicool</category>
      <guid isPermaLink="true">https://itindex.net/detail/60660-%E6%97%B6%E9%97%B4-%E5%A4%96%E5%8D%96-%E9%80%81%E8%BE%BE</guid>
      <pubDate>Tue, 09 Jun 2020 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>时间序列异常检测算法梳理</title>
      <link>https://itindex.net/detail/60453-%E6%97%B6%E9%97%B4%E5%BA%8F%E5%88%97-%E5%BC%82%E5%B8%B8%E6%A3%80%E6%B5%8B-%E7%AE%97%E6%B3%95</link>
      <description>&lt;h2&gt;异常的分类&lt;/h2&gt;
 &lt;p&gt;时间序列的异常检测问题通常表示为相对于某些标准信号或常见信号的离群点。虽然有很多的异常类型，但是我们只关注业务角度中最重要的类型，比如意外的峰值、下降、趋势变化以及等级转换（level shifts）。&lt;/p&gt;
 &lt;p&gt;常见的异常有如下几种：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;革新性异常：innovational outlier (IO)，造成离群点的干扰不仅作用于$X_T$，而且影响T时刻以后序列的所有观察值。&lt;/li&gt;
  &lt;li&gt;附加性异常：additive outlier (AO)，造成这种离群点的干扰，只影响该干扰发生的那一个时刻T上的序列值，而不影响该时刻以后的序列值。&lt;/li&gt;
  &lt;li&gt;水平移位异常：level shift (LS)，造成这种离群点的干扰是在某一时刻T，系统的结构发生了变化，并持续影响T时刻以后的所有行为，在数列上往往表现出T时刻前后的序列均值发生水平位移。&lt;/li&gt;
  &lt;li&gt;暂时变更异常temporary change (TC)：造成这种离群点的干扰是在T时刻干扰发生时具有一定初始效应，以后随时间根据衰减因子的大小呈指数衰减。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;上面的解释可能不太容易理解，我们结合图片来看一下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="856" src="https://www.biaodianfu.com/wp-content/uploads/2020/03/outlier.png" width="859"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;通常，异常检测算法应该将每个时间点标记为异常/非异常，或者预测某个点的信号，并衡量这个点的真实值与预测值的差值是否足够大，从而将其视为异常。使用后面的方法，你将能够得到一个可视化的置信区间，这有助于理解为什么会出现异常并进行验证。&lt;/p&gt;
 &lt;h2&gt;常见异常检测方法&lt;/h2&gt;
 &lt;p&gt;从分类看，当前发展阶段的时序异常检测算法和模型可以分为一下几类：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="262" src="https://www.biaodianfu.com/wp-content/uploads/2020/03/anomaly-detection.png" width="886"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;统计模型：优点是复杂度低，计算速度快，泛化能力强悍。因为没有训练过程，即使没有前期的数据积累，也可以快速的投入生产使用。缺点是准确率一般。但是这个其实是看场景的，并且也有简单的方法来提高业务层面的准确率。这个后面会提到。&lt;/li&gt;
  &lt;li&gt;机器学习模型：鲁棒性较好，准确率较高。需要训练模型，泛化能力一般。&lt;/li&gt;
  &lt;li&gt;深度学习模型：普遍需要喂大量的数据，计算复杂度高。整体看，准确性高，尤其是近段时间，强化学习的引入，进一步巩固其准确性方面的领先优势。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;3-Sigma&lt;/h3&gt;
 &lt;p&gt;3-Sigma原则又称为拉依达准则，该准则定义如下：假设一组检测数据只含有随机误差，对原始数据进行计算处理得到标准差，然后按一定的概率确定一个区间，认为误差超过这个区间的就属于异常值。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="370" src="https://www.biaodianfu.com/wp-content/uploads/2020/03/3-Sigma.png" width="709"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;使用3-Sigma的前提是数据服从正态分布，满足这个条件之后，在3-Sigma范围$(\mu – 3\sigma ,\mu + 3\sigma)$内99.73%的为正常数据，其中$\sigma$代表标准差,$\mu$代表均值，$x=\mu$为图形的对称轴。下面是3-Sigma的Python实现：&lt;/p&gt; &lt;pre&gt;import numpy as np


def three_sigma(df_col):
    &amp;apos;&amp;apos;&amp;apos;
    df_col：DataFrame数据的某一列
    &amp;apos;&amp;apos;&amp;apos;
    rule = (df_col.mean() - 3 * df_col.std() &amp;gt; df_col) | (df_col.mean() + 3 * df_col.std() &amp;lt; df_col)
    index = np.arange(df_col.shape[0])[rule]
    out_range = df_col.iloc[index]
    return out_range&lt;/pre&gt; &lt;p&gt;对于异常值检测出来的结果，有多种处理方式，如果是时间序列中的值，那么我们可以认为这个时刻的操作属于异常的；如果是将异常值检测用于数据预处理阶段，处理方法有以下四种：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;删除带有异常值的数据；&lt;/li&gt;
  &lt;li&gt;将异常值视为缺失值，交给缺失值处理方法来处理；&lt;/li&gt;
  &lt;li&gt;用平均值进行修正；&lt;/li&gt;
  &lt;li&gt;当然我们也可以选择不处理。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;Grubbs测试&lt;/h3&gt;
 &lt;p&gt;Grubbs’Test为一种假设检验的方法，常被用来检验服从正太分布的单变量数据集（univariate data set）Y 中的单个异常值。若有异常值，则其必为数据集中的最大值或最小值。原假设与备择假设如下：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;$H_0$: 数据集中没有异常值&lt;/li&gt;
  &lt;li&gt;$H_1$: 数据集中有一个异常值&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;Grubbs’ Test检验假设的所用到的检验统计量（test statistic）为：&lt;/p&gt;
 &lt;p&gt;$$G = \frac{\max |Y_i – \overline{Y}|}{s}$$&lt;/p&gt;
 &lt;p&gt;其中，$\overline{Y}$为均值，s为标准差。原假设$H_0$被拒绝，当检验统计量满足以下条件：&lt;/p&gt;
 &lt;p&gt;$$G &amp;gt; \frac{(N-1)}{\sqrt{N}}\sqrt{\frac{ (t_{\alpha/(2N), N-2})^2}{N-2 + (t_{\alpha/(2N), N-2})^2}}$$&lt;/p&gt;
 &lt;p&gt;其中，$N$为数据集的样本数，$t_{\alpha/(2N), N-2}$为显著度(significance level)等于$\alpha/(2N)$、自由度（degrees of freedom）等于$N-2$的t分布临界值。实际上，Grubbs’ Test可理解为检验最大值、最小值偏离均值的程度是否为异常。&lt;/p&gt;
 &lt;p&gt;使用Grubbs测试需要总体是正态分布的。算法流程：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;样本从小到大排序&lt;/li&gt;
  &lt;li&gt;求样本的mean和dev&lt;/li&gt;
  &lt;li&gt;计算min/max与mean的差距，更大的那个为可疑值&lt;/li&gt;
  &lt;li&gt;求可疑值的z-score (standard score)，如果大于Grubbs临界值，那么就是outlier&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;Grubbs临界值可以查表得到，它由两个值决定：检出水平$\alpha$（越严格越小），样本数量n，排除outlier，对剩余序列循环做 1-4 步骤。由于这里需要的是异常判定，只需要判断tail_avg是否outlier即可。&lt;/p&gt;
 &lt;p&gt;这里找到了一个Python包：  &lt;a href="https://pypi.org/project/outlier_utils/"&gt;https://pypi.org/project/outlier_utils/&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;使用示例：&lt;/p&gt; &lt;pre&gt;from outliers import smirnov_grubbs as grubbs


print(grubbs.test([8, 9, 10, 1, 9], alpha=0.05))
print(grubbs.min_test_outliers([8, 9, 10, 1, 9], alpha=0.05))
print(grubbs.max_test_outliers([8, 9, 10, 1, 9], alpha=0.05))
print(grubbs.max_test_indices([8, 9, 10, 50, 9], alpha=0.05))&lt;/pre&gt; &lt;p&gt;&lt;/p&gt;
 &lt;h3&gt;ESD（generalized ESD test）&lt;/h3&gt;
 &lt;p&gt;在现实数据集中，异常值往往是多个而非单个。为了将Grubbs’ Test扩展到k个异常值检测，则需要在数据集中逐步删除与均值偏离最大的值（为最大值或最小值），同步更新对应的t分布临界值，检验原假设是否成立。基于此，Rosner提出了Grubbs’ Test的泛化版ESD（Extreme Studentized Deviate test）。算法流程如下：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;计算与均值偏离最远的残差，注意计算均值时的数据序列应是删除上一轮最大残差样本数据后。$R_j = \frac{\max_i |Y_i – \overline{Y’}|}{s}, 1 \leq j \leq k$&lt;/li&gt;
  &lt;li&gt;计算临界值（critical value）.$\lambda_j = \frac{(n-j) * t_{p,n-j-1}}{\sqrt{(n-j-1+t_{p,n-j-1}^2)(n-j+1)}}, 1 \leq j \leq k$&lt;/li&gt;
  &lt;li&gt;检验原假设，比较检验统计量与临界值；若$R_i &amp;gt; \lambda_j$，则原假设$H_0$不成立，该样本点为异常点&lt;/li&gt;
  &lt;li&gt;重复以上步骤k次至算法结束。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;Python包：  &lt;a href="https://www.hs.uni-hamburg.de/DE/Ins/Per/Czesla/PyA/PyA/index.html"&gt;https://www.hs.uni-hamburg.de/DE/Ins/Per/Czesla/PyA/PyA/index.html&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;使用方法：&lt;/p&gt; &lt;pre&gt;import numpy as np
import matplotlib.pylab as plt
from PyAstronomy import pyasl

# Convert data given at:
# http://www.itl.nist.gov/div898/handbook/eda/section3/eda35h3.htm
# to array.
x = np.array([float(x) for x in &amp;quot;-0.25 0.68 0.94 1.15 1.20 1.26 1.26 1.34 1.38 1.43 1.49 1.49 \
          1.55 1.56 1.58 1.65 1.69 1.70 1.76 1.77 1.81 1.91 1.94 1.96 \
          1.99 2.06 2.09 2.10 2.14 2.15 2.23 2.24 2.26 2.35 2.37 2.40 \
          2.47 2.54 2.62 2.64 2.90 2.92 2.92 2.93 3.21 3.26 3.30 3.59 \
          3.68 4.30 4.64 5.34 5.42 6.01&amp;quot;.split()])

# Apply the generalized ESD
r = pyasl.generalizedESD(x, 10, 0.05, fullOutput=True)

print(&amp;quot;Number of outliers: &amp;quot;, r[0])
print(&amp;quot;Indices of outliers: &amp;quot;, r[1])
print(&amp;quot;        R      Lambda&amp;quot;)
for i in range(len(r[2])):
    print(&amp;quot;%2d  %8.5f  %8.5f&amp;quot; % ((i + 1), r[2][i], r[3][i]))

# Plot the &amp;quot;data&amp;quot;
plt.plot(x, &amp;apos;b.&amp;apos;)
# and mark the outliers.
for i in range(r[0]):
    plt.plot(r[1][i], x[r[1][i]], &amp;apos;rp&amp;apos;)
plt.show()&lt;/pre&gt; &lt;p&gt;&lt;/p&gt;
 &lt;h3&gt;S-ESD与S-H-ESD&lt;/h3&gt;
 &lt;p&gt;鉴于时间序列数据具有周期性（seasonal）、趋势性（trend），异常检测时不能作为孤立的样本点处理；故而Twitter的工程师提出了S- ESD (Seasonal ESD)与S-H-ESD (Seasonal Hybrid ESD)算法，将ESD扩展到时间序列数据。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;STL分解&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;STL (Seasonal-Trend decomposition procedure based on Loess) 为时序分解中一种常见的算法，基于LOESS将某时刻的数据$Y_v$分解为趋势分量（trend component）、季节性分量（seasonal component）和残差（remainder component）:&lt;/p&gt;
 &lt;p&gt;$$Y_v = T _v + S_v + R_v \quad v= 1, \cdots, N$$&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="492" src="https://www.biaodianfu.com/wp-content/uploads/2020/03/stl.png" width="577"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;由上到下依次为：原始时间序列和使用 STL 分解得到的季节变化部分、趋势变化部分以及残差部分。&lt;/p&gt;
 &lt;p&gt;STL分为内循环（inner loop）与外循环（outer loop），其中内循环主要做了趋势拟合与周期分量的计算。假定$T_v^{(k)}$、Sv(k)为内循环中第k-1次pass结束时的趋势分量、周期分量，初始时T(k)v=0；并有以下参数：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;n(i)内层循环数&lt;/li&gt;
  &lt;li&gt;n(o)外层循环数&lt;/li&gt;
  &lt;li&gt;n(p)为一个周期的样本数&lt;/li&gt;
  &lt;li&gt;n(s)为Step 2中LOESS平滑参数&lt;/li&gt;
  &lt;li&gt;n(l)为Step 3中LOESS平滑参数&lt;/li&gt;
  &lt;li&gt;n(t)为Step 6中LOESS平滑参数&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;每个周期相同位置的样本点组成一个子序列（subseries），容易知道这样的子序列共有共有n(p)个，我们称其为cycle-subseries。内循环主要分为以下6个步骤：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Step 1: 去趋势（Detrending），减去上一轮结果的趋势分量，$Y_v – T_v^{(k)}$&lt;/li&gt;
  &lt;li&gt;Step 2: 周期子序列平滑（Cycle-subseries smoothing），用$\text{LOESS}(q=n_{n(s)}, d=1)$对每个子序列做回归，并向前向后各延展一个周期；平滑结果组成temporary seasonal series，记为$C_v^{(k+1)}, v = -n_{(p)} + 1, \cdots, -N + n_{(p)} $&lt;/li&gt;
  &lt;li&gt;Step 3: 周期子序列的低通量过滤（Low-Pass Filtering），对上一个步骤的结果序列$C_v^{(k+1)}$依次做长度为n(p)、n(p)、3的滑动平均（moving average），然后做$\text{LOESS}(q=n_{n(l)}, d=1)$回归，得到结果序列$L_v^{(k+1)}, v = 1, \cdots, N$；相当于提取周期子序列的低通量&lt;/li&gt;
  &lt;li&gt;Step 4: 去除平滑周期子序列趋势（Detrending of Smoothed Cycle-subseries），$S_v^{(k+1)} = C_v^{(k+1)} – L_v^{(k+1)}$&lt;/li&gt;
  &lt;li&gt;Step 5: 去周期（Deseasonalizing），减去周期分量，$Y_v – S_v^{(k+1)}$&lt;/li&gt;
  &lt;li&gt;Step 6: 趋势平滑（Trend Smoothing），对于去除周期之后的序列做$\text{LOESS}(q=n_{n(t)}, d=1)$回归，得到趋势分量$T_v^{(k+1)}$。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;外层循环主要用于调节robustness weight。如果数据序列中有outlier，则余项会较大。定义：&lt;/p&gt;
 &lt;p&gt;$$h = 6 * median(|R_v|)$$&lt;/p&gt;
 &lt;p&gt;对于位置为v的数据点，其robustness weight为：&lt;/p&gt;
 &lt;p&gt;$$\rho_v = B(|R_v|/h)$$&lt;/p&gt;
 &lt;p&gt;其中B函数为bisquare函数：&lt;/p&gt;
 &lt;p&gt;$$B(u) = \left\{\begin{matrix}(1-u^2)^2 &amp;amp; 0 \le u &amp;lt; 1 \\ 0 &amp;amp; u \ge 1 \end{matrix}\right.$$&lt;/p&gt;
 &lt;p&gt;然后每一次迭代的内循环中，在Step 2与Step 6中做LOESS回归时，邻域权重（neighborhood weight）需要乘以$\rho_v$，以减少outlier对回归的影响。STL的具体流程如下：&lt;/p&gt; &lt;pre&gt;outer loop:
    计算robustness weight;
    inner loop:
        Step 1 去趋势;
        Step 2 周期子序列平滑;
        Step 3 周期子序列的低通量过滤;
        Step 4 去除平滑周期子序列趋势;
        Step 5 去周期;
        Step 6 趋势平滑;&lt;/pre&gt; &lt;p&gt;为了使得算法具有足够的robustness，所以设计了内循环与外循环。特别地，当n(i)足够大时，内循环结束时趋势分量与周期分量已收敛；若时序数据中没有明显的outlier，可以将n(o)设为0。&lt;/p&gt;
 &lt;p&gt;Python的statsmodels实现了一个简单版的时序分解，通过加权滑动平均提取趋势分量，然后对cycle-subseries每个时间点数据求平均组成周期分量：&lt;/p&gt;
 &lt;p&gt;使用示例：&lt;/p&gt; &lt;pre&gt;import numpy as np
import pandas as pd
from statsmodels.tsa.seasonal import seasonal_decompose
import matplotlib.pyplot as plt

# Generate some data
np.random.seed(0)
n = 1500
dates = np.array(&amp;apos;2019-01-01&amp;apos;, dtype=np.datetime64) + np.arange(n)
data = 12 * np.sin(2 * np.pi * np.arange(n) / 365) + np.random.normal(12, 2, 1500)
df = pd.DataFrame({&amp;apos;data&amp;apos;: data}, index=dates)

# Reproduce the example in OP
seasonal_decompose(df, model=&amp;apos;additive&amp;apos;, period=1).plot()
plt.show()&lt;/pre&gt; &lt;p&gt;参考链接：  &lt;a href="https://www.statsmodels.org/stable/generated/statsmodels.tsa.seasonal.seasonal_decompose.html"&gt;https://www.statsmodels.org/stable/generated/statsmodels.tsa.seasonal.seasonal_decompose.html&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;S-ESD&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;STL将时间序列数据分解为趋势分量、周期分量和余项分量。想当然的解法——将ESD运用于STL分解后的余项分量中，即可得到时间序列上的异常点。但是，我们会发现在余项分量中存在着部分假异常点（spurious anomalies）。如下图所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="295" src="https://www.biaodianfu.com/wp-content/uploads/2020/03/s-esd.png" width="946"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在红色矩形方框中，向下突起点被误报为异常点。为了解决这种假阳性降低准确率的问题，S-ESD算法用中位数（median）替换掉趋势分量；余项计算公式如下：&lt;/p&gt;
 &lt;p&gt;$$R_X = X – S_X- \tilde{X}$$&lt;/p&gt;
 &lt;p&gt;其中，$X$为原时间序列数据，$S_X$为STL分解后的周期分量，$\tilde{X}$为$X$的中位数。&lt;/p&gt;
 &lt;p&gt;Python包：  &lt;a href="https://github.com/nachonavarro/seasonal-esd-anomaly-detection"&gt;https://github.com/nachonavarro/seasonal-esd-anomaly-detection&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;使用示例：&lt;/p&gt; &lt;pre&gt;import numpy as np
import sesd
ts = np.random.random(100)
# Introduce artificial anomalies
ts[14] = 9
ts[83] = 10
outliers_indices = sesd.seasonal_esd(ts, periodicity=20, hybrid=True, max_anomalies=2)
for idx in outliers_indices:
    print(f&amp;apos;Anomaly index: {idx}, anomaly value: {ts[idx]}&amp;apos;)&lt;/pre&gt; &lt;p&gt;  &lt;strong&gt;S-H-ESD&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;由于个别异常值会极大地拉伸均值和方差，从而导致S-ESD未能很好地捕获到部分异常点，召回率偏低。为了解决这个问题，S-H-ESD采用了更具鲁棒性的中位数与绝对中位差（Median Absolute Deviation, MAD）替换公式中的均值与标准差。MAD的计算公式如下：&lt;/p&gt;
 &lt;p&gt;$$\text{MAD} = median(|X_i – median(X)|)$$&lt;/p&gt;
 &lt;p&gt;Python包：  &lt;a href="https://github.com/zrnsm/pyculiarity"&gt;https://github.com/zrnsm/pyculiarity&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;使用示例：&lt;/p&gt; &lt;pre&gt;from pyculiarity import detect_ts
import matplotlib.pyplot as plt
import pandas as pd

# first run the models
twitter_example_data = pd.read_csv(&amp;apos;./raw_data.csv&amp;apos;, usecols=[&amp;apos;timestamp&amp;apos;, &amp;apos;count&amp;apos;])
results = detect_ts(twitter_example_data, max_anoms=0.05, alpha=0.001, direction=&amp;apos;both&amp;apos;, only_last=None)

# format the twitter data nicely
twitter_example_data[&amp;apos;timestamp&amp;apos;] = pd.to_datetime(twitter_example_data[&amp;apos;timestamp&amp;apos;])
twitter_example_data.set_index(&amp;apos;timestamp&amp;apos;, drop=True)

# make a nice plot
f, ax = plt.subplots(2, 1, sharex=True)
ax[0].plot(twitter_example_data[&amp;apos;timestamp&amp;apos;], twitter_example_data[&amp;apos;value&amp;apos;], &amp;apos;b&amp;apos;)
ax[0].plot(results[&amp;apos;anoms&amp;apos;].index, results[&amp;apos;anoms&amp;apos;][&amp;apos;anoms&amp;apos;], &amp;apos;ro&amp;apos;)
ax[0].set_title(&amp;apos;Detected Anomalies&amp;apos;)
ax[1].set_xlabel(&amp;apos;Time Stamp&amp;apos;)
ax[0].set_ylabel(&amp;apos;Count&amp;apos;)
ax[1].plot(results[&amp;apos;anoms&amp;apos;].index, results[&amp;apos;anoms&amp;apos;][&amp;apos;anoms&amp;apos;], &amp;apos;b&amp;apos;)
ax[1].set_ylabel(&amp;apos;Anomaly Magnitude&amp;apos;)
plt.show()&lt;/pre&gt; &lt;p&gt;  &lt;img alt="" height="344" src="https://www.biaodianfu.com/wp-content/uploads/2020/03/S-H-ESD.png" width="468"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;R语言版：  &lt;a href="https://github.com/twitter/AnomalyDetection"&gt;https://github.com/twitter/AnomalyDetection&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;移动平均/加权移动平均/指数加权移动平均&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;移动平均 moving average&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;给定一个时间序列和窗口长度N，moving average等于当前data point之前N个点（包括当前点）的平均值。不停地移动这个窗口，就得到移动平均曲线。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;累加移动平均 cumulative moving average&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;设$\{x_i:i \ge 1\}$是观察到的数据序列。 累积移动平均线是所有数据的未加权平均值。 如果若干天的值是$x_1,…,x_i$，那么$CMA_i=\frac{x_1+…+x_i}{i}$&lt;/p&gt;
 &lt;p&gt;当有新的值$ x_{i+1} $，那么累积移动平均为：&lt;/p&gt;
 &lt;p&gt;$$\begin{align*} CMA_{i+1} &amp;amp;= \frac{x_1+…+x_i+x_{i+1}}{i+1} \\ &amp;amp; = \frac{x_{i+1}+n·CMA_i}{i+1}\\ &amp;amp; = CMA_i + \frac{x_{i+1}-CMA_i}{i+1} \end{align*}$$&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;加权移动平均 weighted moving average&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;加权移动平均值是先前w个数据的加权平均值。假设$\sum_{j=0}^{w-1}weight_j=1$，其中$weight_j&amp;gt;0$，则加权移动平均值为$WMA_i=\sum_{j=0}^{w-1}weight_j \dot x_{i-j}$&lt;/p&gt;
 &lt;p&gt;一般$weight_j = \frac{w-j}{w+(w-1)+…+1}, for\ 0\le j \le w-1$，所以：$WMA_i=\frac{wx_i+(w-1)x_{i-1}+…+2x_{i-w+2}+x_{i-w+1}}{w+(w-1)+…+1}$&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;指数加权移动平均 exponential weighted moving average&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;指数移动与移动平均有些不同：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;并没有时间窗口，用的是从时间序列第一个data point到当前data point之间的所有点。&lt;/li&gt;
  &lt;li&gt;每个data point的权重不同，离当前时间点越近的点的权重越大，历史时间点的权重随着离当前时间点的距离呈指数衰减，从当前data point往前的data point，权重依次为$\alpha ,\alpha (1-\alpha),\alpha (1-\alpha)^2,…,\alpha (1-\alpha)^n$。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;该算法可以检测一个异常较短时间后发生另外一个异常的情况，异常持续一段时间后可能被判定为正常。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;双指数平滑 double exponential smoothing (Holt-Linear)&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;假设$\{Y_t:t\ge1\}$是观测数据序列，有两个与双指数平滑相关的方程：&lt;/p&gt;
 &lt;p&gt;$$S_t = \alpha Y_t+(1-\alpha)(S_{t-1}+b_{t-1})$$&lt;/p&gt;
 &lt;p&gt;$$b_t = \beta(S_t-S_{t-1})+(1-\beta)b_{t-1}$$&lt;/p&gt;
 &lt;p&gt;其中$\alpha \in [0,1]$是数据平滑因子，$\beta \in [0,1]$是趋势平滑因子。&lt;/p&gt;
 &lt;p&gt;这里，初始值为$S_1=Y_1$，$b_1$有三种可能：&lt;/p&gt;
 &lt;p&gt;$$b_1 = Y_2-Y_1$$&lt;/p&gt;
 &lt;p&gt;$$b_1 = \frac{(Y_2-Y_1)+(Y_3-Y_2)+(Y_4-Y_3)}{3}=\frac{Y_4-Y_1}{3}$$&lt;/p&gt;
 &lt;p&gt;$$b_1 = \frac{Y_n-Y_1}{n-1}$$&lt;/p&gt;
 &lt;p&gt;预测：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;单个时刻：$F_{t+1}=S_t+b_t$&lt;/li&gt;
  &lt;li&gt;m个时刻：$F_{t+m}=S_t+mb_t$&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;三指数平滑 triple exponential smoothing （Holt-Winters）&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;u&gt;multiplicative seasonality&lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;设$\{Y_t:t \ge 1\}$是观察到的数据序列，则三指数平滑为：&lt;/p&gt;
 &lt;p&gt;$$S_t = \alpha \frac{Y_t}{c_{t-L}}+(1-\alpha)(S_{t-1}+b_{t-1}),overall\ smoothing $$&lt;/p&gt;
 &lt;p&gt;$$b_t = \beta(S_t-S_{t-1})+(1-\beta)b_{t-1},trend\ smoothing$$&lt;/p&gt;
 &lt;p&gt;$$c_t = \gamma\frac{Y_t}{S_t}+(1-\gamma)c_{t-L}, seasonal smoothing$$&lt;/p&gt;
 &lt;p&gt;其中$\alpha \in [0,1]$是数据平滑因子，$\beta \in [0,1]$是趋势平滑因子，$\gamma \in [0,1]$是季节变化平滑因子。&lt;/p&gt;
 &lt;p&gt;预测m个时刻：$F_{t+m}=(S_t+mb_t)c_{(t-L+m)\ mod\ L}$&lt;/p&gt;
 &lt;p&gt;初始值设定：&lt;/p&gt;
 &lt;p&gt;$$S_1 = Y_1$$&lt;/p&gt;
 &lt;p&gt;$$b_0 = \frac{(Y_{L+1}-Y_1)+(Y_{L+2}-Y_2)+…+(Y_{L+L}-Y_L)}{L}$$&lt;/p&gt;
 &lt;p&gt;$$c_i = \frac{1}{N}\sum_{j=1}^N\frac{Y_{L(j-1)+i}}{A_j}, \forall i \in \{1,…,L\}$$&lt;/p&gt;
 &lt;p&gt;$$A_j=\frac{\sum_{i=1}^LY_{L(j-1)+i}}{L}, \forall \in \{1,…,N\}$$&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;   &lt;u&gt;additive seasonality&lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;设$\{Y_t:t \ge 1\}$是观察到的数据序列，则三指数平滑为：&lt;/p&gt;
 &lt;p&gt;$$S_t = \alpha(Y_t-c_{t-L})+(1-\alpha)(S_{t-1}+b_{t-1}), overall \ smoothing $$&lt;/p&gt;
 &lt;p&gt;$$b_t = \beta(S_t- S_{t-1})+(1-\beta)b_{t-1}, trend \ smoothing $$&lt;/p&gt;
 &lt;p&gt;$$c_t = \gamma(Y_t-S_{t-1}-b_{t-1})+(1-\gamma)c_{t-L}, seasonal smoothing$$&lt;/p&gt;
 &lt;p&gt;其中$\alpha \in [0,1]$是数据平滑因子，$\beta \in [0,1]$是趋势平滑因子，$\gamma \in [0,1]$是季节变化平滑因子。&lt;/p&gt;
 &lt;p&gt;预测m个时刻：$F_{t+m}=S_t+mb_t+c_{(t-L+m)\ mod \ L}$&lt;/p&gt;
 &lt;h3&gt;ARIMA 模型&lt;/h3&gt;
 &lt;p&gt;自回归移动平均模型（  &lt;a href="https://www.biaodianfu.com/time-series-forecasting-codes-python.html"&gt;ARIMA&lt;/a&gt;）是一种设计上非常简单的方法，但其效果足够强大，可以预测信号并发现其中的异常。该方法的思路是从过去的几个数据点来生成下一个数据点的预测，在过程中添加一些随机变量（通常是添加白噪声）。以此类推，预测得到的数据点可以用来生成新的预测。很明显：它会使得后续预测信号数据更平滑。使用这种方法最困难的部分是  &lt;a href="https://en.wikipedia.org/wiki/Box%E2%80%93Jenkins_method"&gt;选择&lt;/a&gt;差异数量、自回归数量和预测误差系数。另一个障碍是信号经过差分后应该是固定的。也就是说，这意味着信号不应该依赖于时间，这是一个比较显著的限制。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="114" src="https://www.biaodianfu.com/wp-content/uploads/2020/03/ARIMA.png" width="722"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;异常检测是利用离群点来建立一个经过调整的信号模型，然后利用  &lt;a href="https://en.wikipedia.org/wiki/T-statistic"&gt;t-统计量&lt;/a&gt;来检验该模型是否比原模型能更好的拟合数据。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="358" src="https://www.biaodianfu.com/wp-content/uploads/2020/03/anomaly.png" width="477"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;该方法最受欢迎的实现是 R 语言中的   &lt;a href="https://cran.r-project.org/web/packages/tsoutliers/tsoutliers.pdf"&gt;tsoutliers&lt;/a&gt; 包。在这种情况下，你可以找到适合信号的 ARIMA 模型，它可以检测出所有类型的异常。&lt;/p&gt;
 &lt;h3&gt;神经网络&lt;/h3&gt;
 &lt;p&gt;与CART方法一样，神经网络有两种应用方式：监督学习和无监督学习。我们处理的数据是时间序列，所以最适合的神经网络类型是 LSTM。如果构建得当，这种循环神经网络将可以建模实现时间序列中最复杂的依赖关系，包括高级的季节性依赖关系。如果存在多个时间序列相互耦合，该方法也非常有用。该领域还在研究中，可以参考这里，构建时序模型需要大量的工作。构建成功完成后，就可能在精确度方面取得优异的成绩。&lt;/p&gt;
 &lt;h2&gt;时间序列异常检测方案&lt;/h2&gt;
 &lt;h3&gt;360时间序列检测机制&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;短期环比（SS）&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;对于时间序列来说，T 时刻的数值对于 T-1 时刻有很强的依赖性。比如流量在8:00很多，在8:01时刻的概率是很大的，但是07:01时刻对于8:01时刻影响不是很大。&lt;/p&gt;
 &lt;p&gt;首先，我们可以使用最近时间窗口（T）内的数据遵循某种趋势的现象来做文章。比如我们将 T 设置为7，则我们取检测值（now_value）和过去7个（记为 i）点进行比较，如果大于阈值我们将 count 加1，如果 count 超过我们设置的 count_num，则认为该点是异常点。&lt;/p&gt;
 &lt;p&gt;$$count(\sum_{i=0}^{T}(nowvalue-i)&amp;gt;threshold)&amp;gt;count\_num$$&lt;/p&gt;
 &lt;p&gt;上面的公式涉及到threshold和count_num两个参数， count_num可以根据的需求进行设置，比如对异常敏感，可以设置count_num小一些，而如果对异常不敏感，可以将count_num设置的大一些。&lt;/p&gt;
 &lt;p&gt;Threshold可以采用动态阈值。业界关于动态阈值设置的方法有很多，通常阈值设置方法会参考过去一段时间内的均值、最大值以及最小值，我们也同样应用此方法。取过去一段时间（比如 T 窗口）的平均值、最大值以及最小值，然后取 max-avg 和 avg-min 的最小值。之所以取最小值的原因是让筛选条件设置的宽松一些，让更多的值通过此条件，减少一些漏报的事件。&lt;/p&gt;
 &lt;p&gt;$$threshold = min(max-avg,avg-min)$$&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;长期环比（LS）&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;短期环比参考的是短期内的数据，而仅仅有短期内的数据是不够的，我们还需要参考更长时间内数据的总体走势。&lt;/p&gt;
 &lt;p&gt;通常使用一条曲线对该趋势进行拟合来反应曲线的走势，如果新的数据打破了这种趋势，使曲线变得不平滑，则该点就出现了异常。曲线拟合的方法有很多，比如回归、moving aver-age、…。在这我们使用 EWMA，即指数权重移动平均方法来拟合曲线。在 EWMA 中，下一点的平均值是由上一点的平均值，加上当前点的实际值修正而来。对于每一个 EWMA 值，每个数据的权重是不一样的，越近的数据将拥有越高的权重。有了平均值之后，我们就可以使用 3-sigma 理论来判断新的 input 是否超过了容忍范围。比较实际的值是否超出了这个范围就可以知道是否可以告警了。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;同比（chain）&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;很多监控项都具有一定的周期性，其中以一天为周期的情况比较常见。为了将监控项的周期性考虑进去，我们选取了某个监控项过去14天的数据。对于某个时刻，将得到14个点可以作为参考值，我们记为 $x_i$，其中 i=1,…,14。&lt;/p&gt;
 &lt;p&gt;我们先考虑静态阈值的方法来判断 input 是否异常（突增和突减）。如果 input 比过去14天同一时刻的最小值乘以一个阈值还小，就会认为该输入为异常点（突减）；而如果 input 比过去14天同一时刻的最大值乘以一个阈值还大，就会认为该输入为异常点（突增）。&lt;/p&gt;
 &lt;p&gt;静态阈值的方法是根据历史经验得出的值，实际中如何给 max_threshold 和 min_threshold 是一个需要讨论的话题。根据目前动态阈值的经验规则来说，取平均值是一个比较好的思路。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;同比振幅（CA）&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;过去14天的历史曲线必然会比今天的曲线低很多。假设今天出了一个小故障，曲线下跌了，相对于过去14天的曲线仍然是高很多的。这样的故障，使用方法二就检测不出来，那么我们将如何改进我们的方法呢？&lt;/p&gt;
 &lt;p&gt;怎么计算 t 时刻的振幅呢？我们使用$ \frac{x_{(t)}-x_{(t-1)}}{x_{(t-1)}}$来表示振幅。举个例子，例如 t 时刻的流量为 900bit，t-1 时刻的是 1000bit，那么可以计算出掉线人数是10%。如果参考过去的数据，我们会得到14个振幅值。使用14个振幅的绝对值作为标准，如果 amplitudethreshold 小于 m 时刻的振幅$\frac{m_{(t)}-m_{(t-1)}}{m_{(t-1)}}$并且 m 时刻的振幅大于0，则我们认为该时刻发生突增，而如果 m 时刻的振幅大于 amplitudethreshold 并且 m 时刻的振幅小于0， 则认为该时刻发生突减。&lt;/p&gt;
 &lt;p&gt;$$amplitude=max[\left | \frac{x_i(t)-x_i(t-1)}{x_i(t-1}\right |],x=1,2,…,14$$&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;算法组合&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;上面介绍了四种方法，这四种方法里面，SS 和 LS 是针对非周期性数据的验证方法，而 chain 和 CA 是针对周期性数据的验证方法。那这四种方法应该如何选择和使用呢？下面我们介绍两种使用方法：&lt;/p&gt;
 &lt;p&gt;1、根据周期性的不同来选择合适的方法。这种方法需要首先验证序列是否具有周期性，如果具有周期性则进入左边分支的检测方法，如果没有周期性则选择进入右分支的检测方法。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="294" src="https://www.biaodianfu.com/wp-content/uploads/2020/03/360-1.png" width="324"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;上面涉及到了如何检测数据周期的问题，我们可以使用差分的方法来检测数据是否具有周期性。比如取最近两天的数据来做差分，如果是周期数据，差分后就可以消除波动，然后结合方差阈值判断的判断方法来确定数据的周期性。当然，如果数据波动范围比较大，可以在差分之前先对数据进行归一化（比如 z-score）。&lt;/p&gt;
 &lt;p&gt;2、不区分周期性，直接根据“少数服从多数”的方法来去检测，这种方法比较好理解，在此就不说明了。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="230" src="https://www.biaodianfu.com/wp-content/uploads/2020/03/360-2.png" width="354"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;原文链接：  &lt;a href="http://blog.cloud.360.cn/post/timeseries_anomaly_detection.html"&gt;http://blog.cloud.360.cn/post/timeseries_anomaly_detection.html&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;美团外卖订单量预测异常报警模型&lt;/h3&gt;
 &lt;p&gt;异常检测主要有两种策略：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;异常驱动的异常检测（敏感性）：宁愿误报，也不能错过任何一个异常，这适用于非常重要的检测。简单概括，就是“宁可错杀一千，不能放过一个”。&lt;/li&gt;
  &lt;li&gt;预算驱动的异常检测（准确性）：这种策略的异常检测，从字面理解就是只有定量的一些预算去处理这些报警，那么只能当一定是某种问题时，才能将报警发送出来。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;这两种策略不可兼容的。对于检测模型的改善，可以从两个方面入手，一是预测器的优化，二是比较器的优化。我们从这两个方面描述模型的改善。&lt;/p&gt;
 &lt;p&gt;预测器，就是用一批历史数据预测当前的数据。使用的历史数据集大小，以及使用的预测算法都会影响最终的预测效果。外卖订单量具有明显的周期性，同时相邻时刻的订单量数据也有很强的相关性，我们的目标，就是使用上面说的相关数据预测出当前的订单量。下面，我们分析几种常用的预测器实现。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;同比环比预测器&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;同比环比是比较常用的异常检测方式，它是将当前时刻数据和前一时刻数据（环比）或者前一天同一时刻数据（同比）比较，超过一定阈值即认为该点异常。如果将不同日期、时刻的监控数据以矩阵方式存储，每一行表示一天内不同时刻的监控数据，每一列表示同一时刻不同日期的监控数据，那么存储矩阵如下图所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="282" src="https://www.biaodianfu.com/wp-content/uploads/2020/03/meituan-1.png" width="362"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;假如需要预测图中黄色数据，那么环比使用图中的蓝色数据作为预测黄点的源数据，同比使用图中红色数据作为预测黄点的源数据。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;基线预测器&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;同比环比使用历史上的单点数据来预测当前数据，误差比较大。t时刻的监控数据，与 t-1,t-2,…时刻的监控数据存在相关性。同时，与t-k,t-2k,…时刻的数据也存在相关性（k为周期），如果能利用上这些相关数据对t时刻进行预测，预测结果的误差将会更小。&lt;/p&gt;
 &lt;p&gt;比较常用的方式是对历史数据求平均，然后过滤噪声，可以得到一个平滑的曲线（基线），使用基线数据来预测当前时刻的数据。该方法预测t时刻数据（图中黄色数据）使用到的历史数据如下图所示（图中红色数据）：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="266" src="https://www.biaodianfu.com/wp-content/uploads/2020/03/meituan-2.png" width="352"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;基线数据预测器广泛应用在业务大盘监控中，预测效果如图所示。从图中可以看出，基线比较平滑，在低峰期预测效果比较好，但是在外卖的午高峰和晚高峰预测误差比较大。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="260" src="https://www.biaodianfu.com/wp-content/uploads/2020/03/meituan-3.png" width="344"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Holt-Winters预测器&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;同比环比预测到基线数据预测，使用的相关数据变多，预测的效果也较好。但是基线数据预测器只使用了周期相关的历史数据，没有使用上同周期相邻时刻的历史数据，相邻时刻的历史数据对于当前时刻的预测影响是比较大的。如外卖订单量，某天天气不好，很多用户不愿意出门，那么当天的外卖的订单量就会呈现整体的上涨，这种整体上涨趋势只能从同一周期相邻时刻的历史数据中预测出来。如图，预测图中黄色数据，如果使用上图中所有的红色数据，那么预测效果会更好。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="272" src="https://www.biaodianfu.com/wp-content/uploads/2020/03/meituan-4.png" width="354"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;本文使用了Holt-Winters来实现这一目标。Holt-Winters是三次指数滑动平均算法，它将时间序列数据分为三部分：残差数据a(t)，趋势性数据b(t)，季节性数据s(t)。使用Holt-Winters预测t时刻数据，需要t时刻前包含多个周期的历史数据。相关链接：  &lt;a href="https://en.wikipedia.org/wiki/Exponential_smoothing"&gt;Exponential smoothing&lt;/a&gt;、  &lt;a href="https://www.otexts.org/fpp/7/5"&gt;Holt-Winters seasonal method&lt;/a&gt;。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;外卖报警模型中的预测器&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;在外卖订单量异常检测中，使用Holt-Winters预测器实时预测下一分钟订单量，每次需要至少5天以上的订单量数据才能有较好的预测效果，数据量要求比较大。在实际的异常检测模型中，我们对Holt-Winters预测器进行了简化。预测器的趋势数据表示的是时间序列的总体变化趋势，如果以天为周期看待外卖的订单量时间序列，是没有明显的趋势性的，因此，我们可以去掉其中的趋势数据部分。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;计算序列的周期性数据&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;时间序列的周期性数据不需要实时计算，按周期性更新即可，如外卖订单大盘监控，s(t)只需要每天更新一次即可。对于s(t)的计算，可以有多种方法，可以使用上面提到的Holt-Winters按公式计算出时间序列的周期性数据，或直接使用前一天的监控数据作为当天的周期数据（这两种方式都需要对输入序列进行预处理，保证算法的输入序列不含有异常数据）。也可以将历史数据做平均求出基线作为序列的周期性数据。&lt;/p&gt;
 &lt;p&gt;目前外卖订单中心报警模型采用的是Holt-Winters计算周期数据的方式。在将该模型推广到外卖其他业务线监控时，使用了计算基线数据作为周期数据的方式，这里简单对比一下两种方式的优劣。&lt;/p&gt;
 &lt;p&gt;1、使用Holt-Winters算法计算周期数据&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;优点：如果序列中含有周期性的陡增陡降点，Holt-Winters计算出的周期数据中会保留这些陡增陡降趋势，因此可以准确的预测出这些趋势，不会产生误报。比如外卖订单的提单数据，在每天的某个时刻都有一个定期陡降，使用该方式可以正确的预测出下降的趋势。如图所示，蓝色线是真实数据，棕色线是预测数据，在该时刻，棕色线准确的预测出了下降点。&lt;/li&gt;
  &lt;li&gt;缺点：需要对输入数据进行预处理，去除异常数据。如果输入序列中含有异常数据，使用Holt-Winters时可能会把这些异常数据计算到周期数据中，影响下一周期的预测从而产生误报（Holt-Winters理论上也只是滑动平均的过程，因此如果输入数据中含有比较大的异常数据时，存在这种可能性，实际应用中订单的报警模型也出现过这种误报）。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;img alt="" height="230" src="https://www.biaodianfu.com/wp-content/uploads/2020/03/meituan-5.png" width="383"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;2、历史数据平均求基线&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;优点：计算出的周期数据比较平滑，不需要对输入序列进行预处理，计算过程中可以自动屏蔽掉异常数据的影响，计算过程简单。&lt;/li&gt;
  &lt;li&gt;缺点：周期数据比较平滑，不会出现陡增陡降点，因此对于周期性出现的陡增陡降不能很好的预测，出现误报。比如外卖活动的大盘（如图所示，红线是真实数据，黑线是预测数据），提前下单优惠在每天某个时刻会出现周期性陡降，使用该方式会出现误报。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;img alt="" height="223" src="https://www.biaodianfu.com/wp-content/uploads/2020/03/meituan-6.png" width="436"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;两种求周期数据的方式各有优劣，可以根据各自的监控数据特点选择合适的计算方式。如果监控数据中含有大量的周期性的陡增陡降点，那么推荐使用方式1，可以避免在这些时间点的误报。如果监控数据比较平滑，陡增陡降点很少，那么推荐方式2，计算简单的同时，也能避免因输入数据预处理不好而造成的意料之外的误报。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;残差数据实时预测&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;计算出周期数据后，下一个目标就是对残差数据的预测。实际监控数据与周期数据相减得到残差数据，对残差数据做一次滑动平均，预测出下一刻的残差，将该时刻的残差、周期数据相加即可得到该时刻的预测数据。残差序列的长度设为60，即可以得到比较准确的预测效果。&lt;/p&gt;
 &lt;p&gt;对于实时预测，使用的是当天的周期数据和前60分钟数据。最终的预测结果如图所示，其中蓝色线是真实数据，红色线是预测数据。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="216" src="https://www.biaodianfu.com/wp-content/uploads/2020/03/meituan-7.png" width="404"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;预测器预测出当前时刻订单量的预测值后，还需要与真实值比较来判断当前时刻订单量是否异常。一般的比较器都是通过阈值法，比如实际值超过预测值的一定比例就认为该点出现异常，进行报警。这种方式错误率比较大。在订单模型的报警检测中没有使用这种方式，而是使用了两个串联的Filter，只有当两个Fliter都认为该点异常时，才进行报警，下面简单介绍一下两个Filter的实现。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;离散度Filter：根据预测误差曲线离散程度过滤出可能的异常点。一个序列的方差表示该序列离散的程度，方差越大，表明该序列波动越大。如果一个预测误差序列方差比较大，那么我们认为预测误差的报警阈值相对大一些才比较合理。离散度Filter利用了这一特性，取连续15分钟的预测误差序列，分为首尾两个序列（e1,e2），如果两个序列的均值差大于e1序列方差的某个倍数，我们就认为该点可能是异常点。&lt;/li&gt;
  &lt;li&gt;阈值Filter：根据误差绝对值是否超过某个阈值过滤出可能的异常点。利用离散度Filter进行过滤时，报警阈值随着误差序列波动程度变大而变大，但是在输入数据比较小时，误差序列方差比较小，报警阈值也很小，容易出现误报。所以设计了根据误差绝对值进行过滤的阈值Filter。阈值Filter设计了一个分段阈值函数y=f(x)，对于实际值x和预测值p，只有当|x-p|&amp;gt;f(x)时报警。实际使用中，可以寻找一个对数函数替换分段阈值函数，更易于参数调优。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;最终的外卖订单异常报警模型结构图如图所示，每天会有定时Job从ETL中统计出最近10天的历史订单量，经过预处理模块，去除异常数据，经过周期数据计算模块得到周期性数据。对当前时刻预测时，取60分钟的真实数据和周期性数据，经过实时预测模块，预测出当前订单量。将连续15分钟的预测值和真实值通过比较器，判断当前时刻是否异常。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="624" src="https://www.biaodianfu.com/wp-content/uploads/2020/03/meituan-8.png" width="640"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;原文链接：  &lt;a href="https://tech.meituan.com/2017/04/21/order-holtwinter.html"&gt;https://tech.meituan.com/2017/04/21/order-holtwinter.html&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;其他参考：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://github.com/Microsoft/TagAnomaly"&gt;https://github.com/Microsoft/TagAnomaly&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/baidu/Curve"&gt;https://github.com/baidu/Curve&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/Tencent/Metis"&gt;https://github.com/Tencent/Metis&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/yahoo/egads"&gt;https://github.com/yahoo/egads&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/Netflix/Surus"&gt;https://github.com/Netflix/Surus&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/rob-med/awesome-TS-anomaly-detection"&gt;https://github.com/rob-med/awesome-TS-anomaly-detection&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/yzhao062/anomaly-detection-resources"&gt;https://github.com/yzhao062/anomaly-detection-resources&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/dsmi-lab-ntust/AnomalyDetectionToolbox"&gt;https://github.com/dsmi-lab-ntust/AnomalyDetectionToolbox&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/jeroenjanssens/phd-thesis"&gt;https://github.com/jeroenjanssens/phd-thesis&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://www.xenonstack.com/blog/time-series-analysis/"&gt;https://www.xenonstack.com/blog/time-series-analysis/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;div&gt;
  &lt;p&gt;Related posts:&lt;/p&gt;  &lt;ol&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/ridge-lasso-elasticnet.html" rel="bookmark" title="&amp;#26426;&amp;#22120;&amp;#23398;&amp;#20064;&amp;#31639;&amp;#27861;&amp;#20043;&amp;#23725;&amp;#22238;&amp;#24402;&amp;#12289;Lasso&amp;#22238;&amp;#24402;&amp;#21644;ElasticNet&amp;#22238;&amp;#24402;"&gt;机器学习算法之岭回归、Lasso回归和ElasticNet回归 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/xmnlp.html" rel="bookmark" title="&amp;#33258;&amp;#28982;&amp;#35821;&amp;#35328;&amp;#22788;&amp;#29702;&amp;#20043;&amp;#23567;&amp;#26126;NLP"&gt;自然语言处理之小明NLP &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/maximal-information-coefficient.html" rel="bookmark" title="&amp;#28145;&amp;#20837;&amp;#29702;&amp;#35299;&amp;#26368;&amp;#22823;&amp;#20114;&amp;#20449;&amp;#24687;&amp;#31995;&amp;#25968;"&gt;深入理解最大互信息系数 &lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;  &lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>数据 异常检测</category>
      <guid isPermaLink="true">https://itindex.net/detail/60453-%E6%97%B6%E9%97%B4%E5%BA%8F%E5%88%97-%E5%BC%82%E5%B8%B8%E6%A3%80%E6%B5%8B-%E7%AE%97%E6%B3%95</guid>
      <pubDate>Tue, 24 Mar 2020 22:43:49 CST</pubDate>
    </item>
    <item>
      <title>Springboot 关于日期时间格式化处理方式总结</title>
      <link>https://itindex.net/detail/60426-springboot-%E6%97%A5%E6%9C%9F-%E6%97%B6%E9%97%B4</link>
      <description>&lt;blockquote&gt;
  &lt;p&gt;项目中使用LocalDateTime系列作为DTO中时间的数据类型，但是SpringMVC收到参数后总报错，为了配置全局时间类型转换，尝试了如下处理方式。&lt;/p&gt;
  &lt;p&gt;   &lt;strong&gt;注：本文基于Springboot2.x测试，如果无法生效可能是spring版本较低导致的。PS：如果你的Controller中的LocalDate类型的参数啥注解（RequestParam、PathVariable等）都没加，也是会出错的，因为默认情况下，解析这种参数是使用    &lt;code&gt;ModelAttributeMethodProcessor&lt;/code&gt;进行处理，而这个处理器要通过反射实例化一个对象出来，然后再对对象中的各个参数进行convert，但是LocalDate类没有构造函数，无法反射实例化因此会报错！！！&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h2&gt;完成目标&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;请求入参为 String（指定格式）转 Date，支持get、post（content-type=application/json）&lt;/li&gt;
  &lt;li&gt;返回数据为Date类型转为指定的日期时间格式字符创&lt;/li&gt;
  &lt;li&gt;支持Java8 日期 API，如：   &lt;code&gt;LocalTime&lt;/code&gt;、   &lt;code&gt;localDate&lt;/code&gt; 和    &lt;code&gt;LocalDateTime&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;  &lt;strong&gt;GET请求及POST表单日期时间字符串格式转换&lt;/strong&gt;&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;这种情况要和时间作为Json字符串时区别对待，因为前端json转后端pojo底层使用的是Json序列化Jackson工具（   &lt;code&gt;HttpMessgeConverter&lt;/code&gt;）；而时间字符串作为普通请求参数传入时，转换用的是   &lt;code&gt;Converter&lt;/code&gt;，两者在处理方式上是有区别。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h3&gt;使用自定义参数转换器（Converter）&lt;/h3&gt;
 &lt;p&gt;实现 org.springframework.core.convert.converter.Converter，自定义参数转换器，如下：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;@Configuration
public class DateConverterConfig {
    @Bean
    public Converter localDateConverter() {
      return new Converter() {
            @Override
            public LocalDate convert(String source) {
                return LocalDate.parse(source, DateTimeFormatter.ofPattern(&amp;quot;yyyy-MM-dd&amp;quot;));
            }
        };
    }

    @Bean
    public Converter localDateTimeConverter() {
        return new Converter() {
            @Override
            public LocalDateTime convert(String source) {
                return LocalDateTime.parse(source, DateTimeFormatter.ofPattern(&amp;quot;yyyy-MM-dd HH:mm:ss&amp;quot;));
            }
        };
    }
}
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;点评：以上两个bean会注入到spring mvc的参数解析器（好像叫做  &lt;code&gt;ParameterConversionService&lt;/code&gt;），当传入的字符串要转为LocalDateTime类时，spring会调用该Converter对这个入参进行转换。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;注意：&lt;/strong&gt;  &lt;code&gt;关于自定义的参数转换器 Converter，这里我遇到了一个坑，我再这里详细记录下&lt;/code&gt;，本来我的想法是为了代码精简，将上面匿名内部类的写法精简成lambda表达式的方式：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;    @Bean
    @ConditionalOnBean(name = &amp;quot;requestMappingHandlerAdapter&amp;quot;)
    public Converter localDateConverter() {
        return source -&amp;gt; LocalDate.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT));
    }
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;当我再次启动项目时却出现了异常：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;Caused by: java.lang.IllegalArgumentException: Unable to determine source type &lt;/code&gt;  &lt;s&gt;   &lt;code&gt; and target type  for your Converter [com.example.demo126.config.MappingConverterAdapter$$Lambda$522/817994751]; does the class parameterize those types?
复制代码&lt;/code&gt;&lt;/s&gt;&lt;/pre&gt; &lt;p&gt;&lt;/p&gt; &lt;img src="https://user-gold-cdn.xitu.io/2020/3/7/170b0c9d63bb0bcd?imageView2/0/w/1280/h/960/ignore-error/1"&gt;&lt;/img&gt; &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;百思不得其解，在查阅了资料才得知一二：&lt;/p&gt;
 &lt;p&gt;web项目启动注册  &lt;code&gt;requestMappingHandlerAdapter&lt;/code&gt;的时候会初始化  &lt;code&gt;WebBindingInitializer&lt;/code&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;而  &lt;code&gt;ConfigurableWebBindingInitializer&lt;/code&gt;需要  &lt;code&gt;FormattingConversionService&lt;/code&gt;, 而  &lt;code&gt;FormattingConversionService&lt;/code&gt;会将所有的  &lt;code&gt;Converter&lt;/code&gt;添加进来，添加的时候需要获取泛型信息：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;@Override
public void addFormatters(FormatterRegistry registry) {
    for (Converter converter : getBeansOfType(Converter.class)) {
      registry.addConverter(converter);
    }
    for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
      registry.addConverter(converter);
    }
    for (Formatter formatter : getBeansOfType(Formatter.class)) {
      registry.addFormatter(formatter);
    }
}
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;添加Converter.class 一般是通过接口获取两个泛型的具体类型&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public ResolvableType as(Class type) {
    if (this == NONE) {
      return NONE;
    }
    Class resolved = resolve();
    if (resolved == null || resolved == type) {
      return this;
    }
    for (ResolvableType interfaceType : getInterfaces()) {
      ResolvableType interfaceAsType = interfaceType.as(type);
      if (interfaceAsType != NONE) {
        return interfaceAsType;
      }
    }
    return getSuperType().as(type);
}
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Lambda表达式的接口是  &lt;code&gt;Converter&lt;/code&gt;，并不能得到具体的类型，在窥探了SpringMVC源码后才得知原来如此，既然指导了原因，那解决办法：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;最简单的方法就是不适用Lambda表达式，还是老老实实的使用匿名内部类，这样就不会存在上述问题&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;或者就是等    &lt;code&gt;requestMappingHandlerAdapter&lt;/code&gt;bean注册完成之后再添加自己的    &lt;code&gt;converter&lt;/code&gt;就不会注册到    &lt;code&gt;FormattingConversionService&lt;/code&gt;中&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;@Bean
@ConditionalOnBean(name = &amp;quot;requestMappingHandlerAdapter&amp;quot;)
public Converter localDateTimeConverter() {
  return source -&amp;gt; LocalDateTime.parse(source, DateTimeUtils.DEFAULT_FORMATTER);
}
复制代码&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;还可以对前端传递的string进行正则匹配，如yyyy-MM-dd HH:mm:ss、yyyy-MM-dd、 HH:mm:ss等，进行匹配。以适应多种场景。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;@Component
public class DateConverter implements Converter&amp;lt;String, Date&amp;gt; {
    @Override
    public Date convert(String value) {
        /**
         * 可对value进行正则匹配，支持日期、时间等多种类型转换
         * 这里我偷个懒，在匹配Date日期格式时直接使用了 hutool 为我们已经写好的解析工具类，这里就不重复造轮子了
         * cn.hutool.core.date.DateUtil
         * @param value
         * @return
         */
        return DateUtil.parse(value.trim());
    }
}
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;**注：**这里我偷个懒，在匹配Date日期格式时直接使用了 hutool 为我们已经写好的解析工具类，这里就不重复造轮子了，下面的方法同样使用了该工具类，想要在自己的项目中使用该工具类也很简单，在项目pom文件中引入hutool的依赖就可以了，如下：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;
&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;cn.hutoolgroupId&amp;gt;
  &amp;lt;artifactId&amp;gt;hutool-allartifactId&amp;gt;
  &amp;lt;version&amp;gt;5.1.3version&amp;gt;
dependency&amp;gt;
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;使用Spring注解&lt;/h3&gt;
 &lt;p&gt;使用spring自带注解@DateTimeFormat(pattern = &amp;quot;yyyy-MM-dd&amp;quot;)，如下：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;@DateTimeFormat(pattern = &amp;quot;yyyy-MM-dd&amp;quot;)
private Date startDate;
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果使用了自定义参数转化器，Spring会优先使用该方式进行处理，即Spring注解不生效。&lt;/p&gt;
 &lt;h3&gt;使用ControllerAdvice配合initBinder&lt;/h3&gt;
 &lt;pre&gt;  &lt;code&gt;@ControllerAdvice
public class GlobalExceptionHandler {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                setValue(LocalDate.parse(text, DateTimeFormatter.ofPattern(&amp;quot;yyyy-MM-dd&amp;quot;)));
            }
        });
        binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                setValue(LocalDateTime.parse(text, DateTimeFormatter.ofPattern(&amp;quot;yyyy-MM-dd HH:mm:ss&amp;quot;)));
            }
        });
        binder.registerCustomEditor(LocalTime.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                setValue(LocalTime.parse(text, DateTimeFormatter.ofPattern(&amp;quot;HH:mm:ss&amp;quot;)));
            }
        });
    }
}
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;从名字就可以看出来，这是在controller做环切（这里面还可以全局异常捕获），在参数进入handler之前进行转换；转换为我们相应的对象。&lt;/p&gt;
 &lt;h2&gt;JSON入参及返回值全局处理&lt;/h2&gt;
 &lt;p&gt;请求类型为:post,content-type=application/json， 后台用  &lt;code&gt;@RequestBody&lt;/code&gt;接收，默认接收及返回值格式为:   &lt;code&gt;yyyy-MM-dd HH:mm:ss&lt;/code&gt;&lt;/p&gt;
 &lt;h3&gt;修改 application.yml 文件&lt;/h3&gt;
 &lt;p&gt;在application.propertities文件中增加如下内容：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;ul&gt;
  &lt;li&gt;支持（content-type=application/json）请求中格式为    &lt;code&gt;yyyy-MM-dd HH:mm:ss&lt;/code&gt;的字符串，后台用   &lt;code&gt;@RequestBody&lt;/code&gt;接收，及返回值date转为   &lt;code&gt;yyyy-MM-dd HH:mm:ss&lt;/code&gt;格式string；&lt;/li&gt;
  &lt;li&gt;不支持（content-type=application/json）请求中yyyy-MM-dd等类型的字符串转为date；&lt;/li&gt;
  &lt;li&gt;不支持java8日期api;&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;利用Jackson的JSON序列化和反序列化&lt;/h3&gt;
 &lt;pre&gt;  &lt;code&gt;@Configuration
public class JacksonConfig {

    /** 默认日期时间格式 */
    public static final String DEFAULT_DATE_TIME_FORMAT = &amp;quot;yyyy-MM-dd HH:mm:ss&amp;quot;;
    /** 默认日期格式 */
    public static final String DEFAULT_DATE_FORMAT = &amp;quot;yyyy-MM-dd&amp;quot;;
    /** 默认时间格式 */
    public static final String DEFAULT_TIME_FORMAT = &amp;quot;HH:mm:ss&amp;quot;;

    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();

        // 忽略json字符串中不识别的属性
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // 忽略无法转换的对象 
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        // PrettyPrinter 格式化输出
        objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
        // NULL不参与序列化
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

        // 指定时区
        objectMapper.setTimeZone(TimeZone.getTimeZone(&amp;quot;GMT+8:00&amp;quot;));
        // 日期类型字符串处理
        objectMapper.setDateFormat(new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT));

        // java8日期日期处理
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
        objectMapper.registerModule(javaTimeModule);

        converter.setObjectMapper(objectMapper);
        return converter;
    }
}
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;总结：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;支持（content-type=application/json）请求中格式为   &lt;code&gt;yyyy-MM-dd HH:mm:ss&lt;/code&gt;的字符串，后台用   &lt;code&gt;@RequestBody&lt;/code&gt;接收，及返回值Date转为   &lt;code&gt;yyyy-MM-dd HH:mm:ss&lt;/code&gt;格式String；&lt;/li&gt;
  &lt;li&gt;支持java8日期api；&lt;/li&gt;
  &lt;li&gt;不支持（content-type=application/json）请求中   &lt;code&gt;yyyy-MM-dd&lt;/code&gt;等类型的字符串转为Date；&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;以上两种方式为JSON入参的全局化处理，推荐使用方式二，尤其适合大型项目在基础包中全局设置。&lt;/p&gt;
 &lt;h2&gt;JSON入参及返回值局部差异化处理&lt;/h2&gt;
 &lt;p&gt;场景： 假如全局日期时间处理格式为：  &lt;code&gt;yyyy-MM-dd HH:mm:ss&lt;/code&gt;，但是某个字段要求接收或返回日期  &lt;code&gt;yyyy-MM-dd&lt;/code&gt;。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;方式一&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;使用springboot自带的注解  &lt;code&gt;@JsonFormat(pattern = &amp;quot;yyyy-MM-dd&amp;quot;)&lt;/code&gt;，如下所示：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;@JsonFormat(pattern = &amp;quot;yyyy-MM-dd&amp;quot;, timezone=&amp;quot;GMT+8&amp;quot;)
private Date releaseDate;
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;  &lt;strong&gt;点评：&lt;/strong&gt; springboot默认提供，功能强大，满足常见场景使用，并可指定时区。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;方式二&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;自定义日期序列化与反序列化，如下所示：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;/**
 * 日期序列化
 */
public class DateJsonSerializer extends JsonSerializer&amp;lt;Date&amp;gt; {
    @Override
    public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        SimpleDateFormat dateFormat = new SimpleDateFormat(&amp;quot;yyyy-MM-dd&amp;quot;);
        jsonGenerator.writeString(dateFormat.format(date));
    }
}

/**
 * 日期反序列化
 */
public class DateJsonDeserializer extends JsonDeserializer&amp;lt;Date&amp;gt; {
    @Override
    public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        try {
            SimpleDateFormat dateFormat = new SimpleDateFormat(&amp;quot;yyyy-MM-dd&amp;quot;);
            return dateFormat.parse(jsonParser.getText());
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
}

/**
 * 使用方式
 */
@JsonSerialize(using = DateJsonSerializer.class)
@JsonDeserialize(using = DateJsonDeserializer.class)
private Date releaseDate;
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;日期时间格式化处理方式完整配置&lt;/h2&gt;
 &lt;pre&gt;  &lt;code&gt;@Configuration
public class DateHandlerConfig {

    /** 默认日期时间格式 */
    public static final String DEFAULT_DATE_TIME_FORMAT = &amp;quot;yyyy-MM-dd HH:mm:ss&amp;quot;;
    /** 默认日期格式 */
    public static final String DEFAULT_DATE_FORMAT = &amp;quot;yyyy-MM-dd&amp;quot;;
    /** 默认时间格式 */
    public static final String DEFAULT_TIME_FORMAT = &amp;quot;HH:mm:ss&amp;quot;;

    /**
     * LocalDate转换器，用于转换RequestParam和PathVariable参数
     * `@ConditionalOnBean(name = &amp;quot;requestMappingHandlerAdapter&amp;quot;)`: 等requestMappingHandlerAdapter bean注册完成之后
     * 再添加自己的`converter`就不会注册到`FormattingConversionService`中
     */
    @Bean
    @ConditionalOnBean(name = &amp;quot;requestMappingHandlerAdapter&amp;quot;)
    public Converter localDateConverter() {
        return source -&amp;gt; LocalDate.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT));
    }

    /**
     * LocalDateTime转换器，用于转换RequestParam和PathVariable参数
     */
    @Bean
    @ConditionalOnBean(name = &amp;quot;requestMappingHandlerAdapter&amp;quot;)
    public Converter localDateTimeConverter() {
        return source -&amp;gt; LocalDateTime.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT));
    }

    /**
     * LocalTime转换器，用于转换RequestParam和PathVariable参数
     */
    @Bean
    @ConditionalOnBean(name = &amp;quot;requestMappingHandlerAdapter&amp;quot;)
    public Converter localTimeConverter() {
        return source -&amp;gt; LocalTime.parse(source, DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT));
    }

    /**
     * Date转换器，用于转换RequestParam和PathVariable参数
     * 这里关于解析各种格式的日期格式采用了 hutool 的日期解析工具类
     */
    @Bean
    public Converter dateConverter() {
        return new Converter() {
            @Override
            public Date convert(String source) {
                return DateUtil.parse(source.trim());
            }
        };
    }

    /**
     * Json序列化和反序列化转换器，用于转换Post请求体中的json以及将我们的对象序列化为返回响应的json
     */
    @Bean
    public ObjectMapper objectMapper(){
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);

        //LocalDateTime系列序列化和反序列化模块，继承自jsr310，我们在这里修改了日期格式
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
        javaTimeModule.addSerializer(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
        javaTimeModule.addSerializer(LocalTime.class,new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
        javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
        javaTimeModule.addDeserializer(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
        javaTimeModule.addDeserializer(LocalTime.class,new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));


        //Date序列化和反序列化
        javaTimeModule.addSerializer(Date.class, new JsonSerializer&amp;lt;&amp;gt;() {
            @Override
            public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
                SimpleDateFormat formatter = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);
                String formattedDate = formatter.format(date);
                jsonGenerator.writeString(formattedDate);
            }
        });
        javaTimeModule.addDeserializer(Date.class, new JsonDeserializer&amp;lt;&amp;gt;() {
            @Override
            public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
                SimpleDateFormat format = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);
                String date = jsonParser.getText();
                try {
                    return format.parse(date);
                } catch (ParseException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        objectMapper.registerModule(javaTimeModule);
        return objectMapper;
    }
}
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;扩充源码：深入研究SpringMVC数据绑定过程&lt;/h2&gt;
 &lt;blockquote&gt;
  &lt;p&gt;接下来进入debug模式，看看mvc是如何将我们request中的参数绑定到我们controller层方法入参的；&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;写一个简单controller，打个断点看看方法调用栈：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;    @GetMapping(&amp;quot;/getDate&amp;quot;)
    public LocalDateTime getDate(@RequestParam LocalDate date,
                                 @RequestParam LocalDateTime dateTime,
                                 @RequestParam Date originalDate) {
        System.out.println(date);
        System.out.println(dateTime);
        System.out.println(originalDate);
        return LocalDateTime.now();
    }
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;调用接口以后，我们看下方法调用栈中一些关键方法：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;//进入DispatcherServlet
doService:942, DispatcherServlet
//处理请求
doDispatch:1038, DispatcherServlet
//生成调用链（前处理、实际调用方法、后处理）
handle:87, AbstractHandlerMethodAdapter
//反射获取到实际调用方法，准备开始调用
invokeHandlerMethod:895, RequestMappingHandlerAdapter
invokeAndHandle:102, ServletInvocableHandlerMethod
//这里是关键，参数从这里开始获取到
invokeForRequest:142, InvocableHandlerMethod
doInvoke:215, InvocableHandlerMethod
//这个是Java reflect调用，因此一定是在这之前获取到的参数
invoke:566, Method
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;根据上述分析，发现  &lt;code&gt;invokeForRequest:142, InvocableHandlerMethod&lt;/code&gt;这里的代码是用来拿到实际参数的：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;    @Nullable
    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
        //这个方法是获取参数的，在这里下个断
        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        if (logger.isTraceEnabled()) {
            logger.trace(&amp;quot;Arguments: &amp;quot; + Arrays.toString(args));
        }
        //这里开始调用方法
        return doInvoke(args);
    }
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;进入这个方法看看是什么操作：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    //获取方法参数数组，包含了入参信息，比如类型、泛型等等
    MethodParameter[] parameters = getMethodParameters();
    //这个用来存放一会从request parameter转换的参数
    Object[] args = new Object[parameters.length];
    for (int i = 0; i &amp;lt; parameters.length; i++) {
      MethodParameter parameter = parameters[i];
      parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
      //这里看起来没啥卵用（providedArgs为空）
      args[i] = resolveProvidedArgument(parameter, providedArgs);
      //这里开始获取到方法实际调用的参数，步进
      if (this.argumentResolvers.supportsParameter(parameter)) {
        //从名字就看出来：参数解析器解析参数
        args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
        continue;
      }
    }
    return args;
}
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;进入resolveArgument看看：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                              NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
  //根据方法入参，获取对应的解析器
  HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
  //开始解析参数（把请求中的parameter转为方法的入参）
  return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这里根据参数获取相应的参数解析器，看看内部如何获取的：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;//遍历，调用supportParameter方法，跟进看看
for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
  if (methodArgumentResolver.supportsParameter(parameter)) {
    result = methodArgumentResolver;
    this.argumentResolverCache.put(parameter, result);
    break;
  }
}
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;  &lt;strong&gt;这里，遍历参数解析器，查找有没有适合的解析器！那么，有哪些参数解析器呢(我测试的时候有26个)？？？我列出几个重要的看看，是不是很眼熟！！！&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;{RequestParamMethodArgumentResolver@7686} 
{PathVariableMethodArgumentResolver@8359} 
{RequestResponseBodyMethodProcessor@8366} 
{RequestPartMethodArgumentResolver@8367} 
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我们进入最常用的一个解析器看看他的supportsParameter方法，发现就是通过参数注解来获取相应的解析器的。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;    public boolean supportsParameter(MethodParameter parameter) {
        //如果参数拥有注解@RequestParam，则走这个分支（知道为什么上文要对RequestParam和Json两种数据区别对待了把）
        if (parameter.hasParameterAnnotation(RequestParam.class)) {
            //这个似乎是对Optional类型的参数进行处理的
            if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
                RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
                return (requestParam != null &amp;amp;&amp;amp; StringUtils.hasText(requestParam.name()));
            }
            else {
                return true;
            }
        }
        //......
    }
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;  &lt;strong&gt;也就是说，对于   &lt;code&gt;@RequestParam&lt;/code&gt;和   &lt;code&gt;@RequestBody&lt;/code&gt;以及   &lt;code&gt;@PathVariable&lt;/code&gt;注解的参数，SpringMVC会使用不同的参数解析器进行数据绑定！&lt;/strong&gt;
  &lt;strong&gt;那么，这三种解析器分别使用什么Converter解析参数呢？我们分别进入三种解析器看一看：&lt;/strong&gt;
  &lt;strong&gt;首先看下   &lt;code&gt;RequestParamMethodArgumentResolver&lt;/code&gt;发现内部使用WebDataBinder进行数据绑定，底层使用的是ConversionService （也就是我们的Converter注入的地方）&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
//通过DataBinder进行数据绑定的
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;  &lt;code&gt;//跟进convertIfNecessary()
public  T convertIfNecessary(@Nullable Object value, @Nullable Class requiredType,
                                @Nullable MethodParameter methodParam) throws TypeMismatchException {

  return getTypeConverter().convertIfNecessary(value, requiredType, methodParam);
}
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;  &lt;code&gt;//继续跟进，看到了把
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
if (editor == null &amp;amp;&amp;amp; conversionService != null &amp;amp;&amp;amp; newValue != null &amp;amp;&amp;amp; typeDescriptor != null) {
  TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
  if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
    try {
      return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
    }
    catch (ConversionFailedException ex) {
      // fallback to default conversion logic below
      conversionAttemptEx = ex;
    }
  }
}
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;  &lt;strong&gt;然后看下   &lt;code&gt;RequestResponseBodyMethodProcessor&lt;/code&gt;发现使用的转换器是HttpMessageConverter类型的：&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;//resolveArgument方法内部调用下面进行参数解析
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());

//step into readWithMessageConverters()，我们看到这里的Converter是HttpMessageConverter
for (HttpMessageConverter converter : this.messageConverters) {
  Class&amp;gt; converterType = (Class&amp;gt;) converter.getClass();
  GenericHttpMessageConverter genericConverter =
    (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter) converter : null);
  if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
      (targetClass != null &amp;amp;&amp;amp; converter.canRead(targetClass, contentType))) {
    if (message.hasBody()) {
      HttpInputMessage msgToUse =
        getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
      body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
              ((HttpMessageConverter) converter).read(targetClass, msgToUse));
      body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
    }
    else {
      body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
    }
    break;
  }
}
复制代码&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;  &lt;strong&gt;最后看下   &lt;code&gt;PathVariableMethodArgumentResolver&lt;/code&gt;发现 和RequestParam走的执行路径一致（二者都是继承自AbstractNamedValueMethodArgumentResolver解析器），因此代码就不贴了。&lt;/strong&gt;&lt;/p&gt;
 &lt;h2&gt;总结&lt;/h2&gt;
 &lt;p&gt;如果要转换request传来的参数到我们指定的类型，根据入参注解要进行区分：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;如果是RequestBody，那么通过配置ObjectMapper（这个玩意儿会注入到Jackson的HttpMessagConverter里面，即   &lt;code&gt;MappingJackson2HttpMessageConverter&lt;/code&gt;中）来实现Json格式数据的序列化和反序列化；&lt;/li&gt;
  &lt;li&gt;如果是RequestParam或者PathVariable类型的参数，通过配置Converter实现参数转换（这些Converter会注入到ConversionService中）。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/60426-springboot-%E6%97%A5%E6%9C%9F-%E6%97%B6%E9%97%B4</guid>
      <pubDate>Sat, 07 Mar 2020 15:17:17 CST</pubDate>
    </item>
    <item>
      <title>时间序列趋势判断</title>
      <link>https://itindex.net/detail/60378-%E6%97%B6%E9%97%B4%E5%BA%8F%E5%88%97-%E8%B6%8B%E5%8A%BF</link>
      <description>&lt;p&gt;判断时间序列数据是上升还是下降是我们常见的问题。比如某个股票在过去一年整体趋势是上升还是下降。我们可以通过画图的方式直接观测出上升还是下降。但每次观测图片非常的麻烦，有没有一些数学方法进行检验？&lt;/p&gt;
 &lt;h2&gt;方案一：直接统计法&lt;/h2&gt;
 &lt;p&gt;假设A序列有N个数。则：$S= \sum_{m=1}^{n}(|A_m-A_{m-1}|)$&lt;/p&gt;
 &lt;p&gt;当序列单调时：$S = |A_n-A_0|$，否则$ S &amp;gt; |A_n-A_0|$。&lt;/p&gt;
 &lt;p&gt;求$\frac{|A_n-A_0|}{S}$可以大概标识震荡的幅度，你可以设置阈值来判断即可。&lt;/p&gt;
 &lt;p&gt;该方法存在的问题，又有取得差值的绝对值，所以无法确定时上升还是下降。但是可以确切的知道震荡的幅度。&lt;/p&gt;
 &lt;h2&gt;方案二：斜率法&lt;/h2&gt;
 &lt;p&gt;斜率方法比较的简单，核心时使用最小二乘法将序列拟合成直线。后根据直线的斜率k去取得序列的走势。例如：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;k &amp;gt; 0.1763 表示上升&lt;/li&gt;
  &lt;li&gt;k &amp;lt; -0.1763 表示下降&lt;/li&gt;
  &lt;li&gt;其他，则表示平稳或震荡&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;上面的判断中的0.1763 实际含义为：tan 10°=0.1763，即直线的倾斜角度，你也可以使用其他的值进行替换。&lt;/p&gt;
 &lt;p&gt;最后，如果想要评定震荡幅度情况，则可以通过残差平方和SSE进行评估。&lt;/p&gt;
 &lt;h2&gt;方案三：Cox-Stuart趋势检验&lt;/h2&gt;
 &lt;p&gt;Cox-Stuart是一种不依赖趋势结构的快速判断趋势是否存在的方法。Cox-Stuart趋势存在性检验的理论基础是符号检验。它的检验思想是：直接考虑数据的变化趋势，若数据有上升趋势，那么排在后面的数据的值要比排在前面的数据的值显著的大，反之，若数据有下降趋势，那么排在后面的数据的值要比排在前面的数据的值显著的小，利用前后两个时期不同数据的差值正负来判断数据总的变化趋势。为保证数对同分布，前后两个数的间隔应固定。这就意味着将数据一分为二，自然形成前后数对。Cox-Staut提出最优的拆分点是数列中位于中间位置的数。&lt;/p&gt;
 &lt;p&gt;检验步骤：可以把每一个观察值和相隔大约n/2的另一个观察值配对比较，因此大约有n/2个对子。然后看增长的对子和减少的对子多少来判断总的趋势。&lt;/p&gt;
 &lt;p&gt;具体做法：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;取$x_i$和$x_{i+c}$组成一对$(x_i, x_{i+c})$。这里如果n为偶数，则$c=n/2$，如果n是奇数，则c=(n+1)/2。当n为偶数时，共有n’=c对，而n是奇数时，共有 n’=c-1对。&lt;/li&gt;
  &lt;li&gt;用每一对的两元素差$D_i= x_i-x_{i+c}$的符号来衡量增减。令$S^+$为正的$D_i$的数目，$S^-$为负的$D_i$的数目。显然当正号太对时有下降趋势，反之有增长趋势。在没有趋势的零假设下他们因服从二项分布b(n’,0.5)。&lt;/li&gt;
  &lt;li&gt;用p(+)表示取到正数的概率，用p(-)表示取到负数的概率，这样我们就得到符号检验方法来检验序列是否存在趋势性。&lt;/li&gt;
&lt;/ul&gt;
 &lt;table width="380"&gt;

  &lt;tr&gt;
   &lt;td width="84"&gt;双侧检验&lt;/td&gt;
   &lt;td width="127"&gt;H0：无趋势&lt;/td&gt;
   &lt;td width="169"&gt;H1：有增长或减少趋势&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="84"&gt;左侧检验&lt;/td&gt;
   &lt;td width="127"&gt;H0：无减少趋势&lt;/td&gt;
   &lt;td width="169"&gt;H1：有减少趋势&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="84"&gt;右侧检验&lt;/td&gt;
   &lt;td width="127"&gt;H0：无增加趋势&lt;/td&gt;
   &lt;td width="169"&gt;H1：有增加趋势&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;p&gt;  &lt;img alt="" height="169" src="https://www.biaodianfu.com/wp-content/uploads/2020/02/Cox-Stuart.png" width="602"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;对于显著性水平$\alpha$，如果p值&amp;lt;$\alpha$，拒绝零假设，否则不能拒绝。&lt;/p&gt;
 &lt;h2&gt;方案四：Mann-Kendall趋势检验法&lt;/h2&gt;
 &lt;p&gt;Mann-Kendall(MK)检验(test)(Mann 1945, Kendall 1975, Gilbert 1987) 的目的是统计评估我们所感兴趣的变量，随着时间变化，是否有单调上升或下降的趋势。单调上升（下降）的趋势意味着该变量随时间增加（减少），但此趋势可能是、也可能不是线性的。MK test可替代参数线性回归分析——线性回归可检验线性拟合直线的斜率是否不为零。回归分析要求拟合回归线的残差是正态分布的，MK检验不需要这种假设，MK检验是非参数检验（不要求服从任何分布-distribution free）&lt;/p&gt;
 &lt;p&gt;以下这些假设是MK检验的基础：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;当没有趋势时，随时间获得的数据是独立同分布的。独立的假设是说数据随着时间不是连续相关的。&lt;/li&gt;
  &lt;li&gt;所获得的时间序列上的数据代表了采样时的真是条件。（样本具有代表性）&lt;/li&gt;
  &lt;li&gt;样本的采集、处理和测量方法提供了总体样本中的无偏且具有代表性的观测值。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;MK检验不要求数据是正态分布，也不要求变化趋势——如果存在的话——是线性的。如果有缺失值或者值低于一个或多个检测限制，是可以计算MK检测的，但检测性能会受到不利影响。独立性假设要求样本之间的时间足够大，这样在不同时间收集的测量值之间不存在相关性。&lt;/p&gt;
 &lt;p&gt;MK检验是检验是否拒绝零假设(null hypothesis: $H_0$)，并接受替代假设（alternative hypothesis: $H_a$）：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;$H_0$：没有单调趋势&lt;/li&gt;
  &lt;li&gt;$H_a$：存在单调趋势&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;最初的假设是：$H_0$为真，在拒绝$H_0$并接受$H_a$之前，数据必须要超出合理怀疑——要到达一定的置信度。MK检验的流程：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;将数据按采集时间列出：$x_1,x_2,…,x_n$，即分别在时间1,2,…,n得到的数据。&lt;/li&gt;
  &lt;li&gt;确定所有n(n-1)/2个$x_j-x_k$差值的符号，其中j &amp;gt; k&lt;/li&gt;
  &lt;li&gt;令$sgn(x_j-x_k)$作为指示函数，依据$x_j-x_k$的正负号取值为1,0或-1&lt;/li&gt;
  &lt;li&gt;计算$S = \sum_{k-1}^{n-1}\sum_{j-k+1}^{n}sgn(x_j-x_k)$。即差值为正的数量减去差值为负的数量。如果S是一个正数，那么后一部分的观测值相比之前的观测值会趋向于变大；如果S是一个负数，那么后一部分的观测值相比之前的观测值会趋向于变小。&lt;/li&gt;
  &lt;li&gt;如果n\leq10，依据Gilbert (1987, page 209, Section 16.4.1)中所描述，要在概率表 (Gilbert 1987, Table A18, page 272) 中查找S。如果此概率小于$\alpha$(认为没有趋势时的截止概率)，那就拒绝零假设，认为趋势存在。如果在概率表中找不到n(存在结数据——tied data values——会发生此情况)，就用表中远离0的下一个值。比如S=12，如果概率表中没有S=12，那么就用S=13来处理也是一样的。如果n &amp;gt; 10，则依以下步骤6-10来判断有无趋势。这里遵循的是Gilbert (1987, page 211, Section 16.4.2)中的程序。&lt;/li&gt;
  &lt;li&gt;计算S的方差如下：$\text{VAR}(S)=\frac{1}{18}[n(n-1)(2n+5)-\sum_{p-1}^{g}t_p(t_p-1)(2t_p+5)]$。其中g是结组（tied groups）的数量，$t_p$是第p组的观测值的数量。例如：在观测值的时间序列{23, 24, 29, 6, 29, 24, 24, 29, 23}中有g = 3个结组，相应地，对于结值(tiied value)23有$t_1= 2$、结值24有$t_2=3$、结值29有$t_3=S3$。当因为有相等值或未检测到而出现结时，VAR(S)可以通过Helsel (2005, p. 191)中的结修正方法来调整。&lt;/li&gt;
  &lt;li&gt;计算MK检验统计量Z_{MK}:&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;$$Z_{M K}=\left\{\begin{array}{cl}{\frac{S-1}{\sqrt{V A R(S)}},} &amp;amp; {S&amp;gt;0} \\{0} &amp;amp; {,\quad S=0} \\{\frac{S+1}{\sqrt{V A R(S)}},} &amp;amp; {S&amp;lt;0}\end{array}\right.$$&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;设想我们要测试零假设。$H_0$(没有单调趋势)对比替代假设$H_a$(有单调增趋势)，其1型错误率为$\alpha$，$0&amp;lt;\alpha&amp;lt;0.50$（注意$\alpha$是MK检验错误地拒绝了零假设时可容忍的概率——即MK检验拒绝了零假设是错误地，但这个事情发生概率是$\alpha$，我们可以容忍这个错误）。如果$Z_{MK}\geq Z_{1-\alpha}$，就拒绝零假设$H_0$，接受替代假设$H_a$，其中$Z_{1-\alpha}$是标准正态分布的$100(1-\alpha)^{th}$百分位。&lt;/li&gt;
  &lt;li&gt;测试上面的$H_0$与$H_a$（有单调递减趋势），其1型错误率为$alpha$，$0&amp;lt;\alpha&amp;lt;0.5$，如果Z$Z_{MK}\leq – Z_{1-\alpha}$，就拒绝零假设$H_0$，接受替代假设$H_a$&lt;/li&gt;
  &lt;li&gt;测试上面的$H_0$与$H_a$（有单调递增或递减趋势），其1型错误率为$alpha$，$0&amp;lt;\alpha&amp;lt;0.5$，如果$|Z_{MK}|\geq Z_{\frac{1-\alpha}{2}}$，就拒绝零假设$H_0$，接受替代假设$H_a$，其中竖线代表绝对值。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;方案五：Cochran-Armitage趋势检验&lt;/h2&gt;
 &lt;p&gt;Cochran-Armitage (CA) 趋势检验是一种用于分析1个二分类变量和1个有序分类变量（等级变量）关联性的统计方法，由Cochran和Armtiage创建和完善。线性趋势检验中最常用的一种方法就是Cochran-Armitage趋势检验。因为二分类变量和有序分类变量可以用列联表的形式表示，所以很多人将针对于这类资料的趋势检验称为趋势卡方检验。更简单来说他面向对象是两个变量，其中一个变量有两个可选值，另一个变量是有序排列的。&lt;/p&gt;
 &lt;h2&gt;方案六：numpy.polyfit()&lt;/h2&gt;
 &lt;p&gt;&lt;/p&gt; &lt;pre&gt;import numpy as np
def trendline(index,data, order=1):
    coeffs = np.polyfit(index, list(data), order)
    slope = coeffs[-2]
    return float(slope)

index=[1,2,3,4]
List=[1043,6582,5452,7571]
resultent=trendline(index,List)
print(resultent)&lt;/pre&gt; &lt;p&gt;如果返回的是正数则为增长，如果返回的是负数，则为下降，如果为0则表示没有趋势。具体原理还没有研究。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;以上是目前梳理的一些方案，有些方案比较绕，目前自己也没有搞清楚。先记录下来，后续慢慢再研究。如果你有这方面经验。欢迎留下你的评论。&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;参考连接：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://wenku.baidu.com/view/cec731b981c758f5f61f6760.html"&gt;https://wenku.baidu.com/view/cec731b981c758f5f61f6760.html&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://wenku.baidu.com/view/47df1180d4d8d15abe234edb"&gt;https://wenku.baidu.com/view/47df1180d4d8d15abe234edb&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://blog.csdn.net/liuchengzimozigreat/article/details/87931248"&gt;https://blog.csdn.net/liuchengzimozigreat/article/details/87931248&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/zhukov-msu/TrendAlgorithms"&gt;https://github.com/zhukov-msu/TrendAlgorithms&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://stackoverflow.com/questions/55649356/how-can-i-detect-if-trend-is-increasing-or-decreasing-in-time-series"&gt;https://stackoverflow.com/questions/55649356/how-can-i-detect-if-trend-is-increasing-or-decreasing-in-time-series&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://pypi.org/project/trendet/"&gt;https://pypi.org/project/trendet/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;div&gt;
  &lt;p&gt;Related posts:&lt;/p&gt;  &lt;ol&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/kendall-rank.html" rel="bookmark" title="&amp;#30456;&amp;#20284;&amp;#24230;&amp;#35745;&amp;#31639;&amp;#20043;kendall&amp;#31209;&amp;#30456;&amp;#20851;&amp;#31995;&amp;#25968;"&gt;相似度计算之kendall秩相关系数 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/jaccard-tanimoto.html" rel="bookmark" title="&amp;#30456;&amp;#20284;&amp;#24230;&amp;#35745;&amp;#31639;&amp;#20043;&amp;#26480;&amp;#21345;&amp;#24503;&amp;#30456;&amp;#20284;&amp;#24230;"&gt;相似度计算之杰卡德相似度 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/ridge-lasso-elasticnet.html" rel="bookmark" title="&amp;#26426;&amp;#22120;&amp;#23398;&amp;#20064;&amp;#31639;&amp;#27861;&amp;#20043;&amp;#23725;&amp;#22238;&amp;#24402;&amp;#12289;Lasso&amp;#22238;&amp;#24402;&amp;#21644;ElasticNet&amp;#22238;&amp;#24402;"&gt;机器学习算法之岭回归、Lasso回归和ElasticNet回归 &lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;  &lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>数据 预测</category>
      <guid isPermaLink="true">https://itindex.net/detail/60378-%E6%97%B6%E9%97%B4%E5%BA%8F%E5%88%97-%E8%B6%8B%E5%8A%BF</guid>
      <pubDate>Fri, 21 Feb 2020 17:18:16 CST</pubDate>
    </item>
    <item>
      <title>使用ffmpeg编码时，如何设置恒定码率，并控制好关键帧I帧间隔 - 黑色幽默2018 - 博客园</title>
      <link>https://itindex.net/detail/59609-ffmpeg-%E7%BC%96%E7%A0%81-%E6%81%92%E5%AE%9A</link>
      <description>&lt;div&gt;    &lt;p&gt;      1. 大家在      &lt;a href="http://www.68idc.cn/help/server/linux/20141127134208.html" rel="nofollow" target="_blank"&gt;使用&lt;/a&gt;      &lt;a href="http://www.68idc.cn/help/server/linuxaq/2014031974770.html" rel="nofollow" target="_blank"&gt;ffmpeg&lt;/a&gt;进行视频      &lt;a href="http://www.68idc.cn/help/jiabenmake/qita/20141217144660.html" rel="nofollow" target="_blank"&gt;编码&lt;/a&gt;时，      &lt;a href="http://www.68idc.cn/help/server/linux/20141127134208.html" rel="nofollow" target="_blank"&gt;使用&lt;/a&gt;-b命令，想      &lt;a href="http://www.68idc.cn/help/netjishu/aspnetbuild/2013102859514.html" rel="nofollow" target="_blank"&gt;控制&lt;/a&gt;比特率，却发现结果并没有如我们      &lt;a href="http://www.68idc.cn/help/interjs/ask/20141220146960.html" rel="nofollow" target="_blank"&gt;设置&lt;/a&gt;所愿，通过码流分析器观察视频码流，      &lt;a href="http://www.68idc.cn/help/jiabenmake/qita/20141202137140.html" rel="nofollow" target="_blank"&gt;码率&lt;/a&gt;的波动还是很大的，      &lt;a href="http://www.68idc.cn/help/server/linuxaq/2014031974770.html" rel="nofollow" target="_blank"&gt;ffmpeg&lt;/a&gt;      &lt;a href="http://www.68idc.cn/help/netjishu/aspnetbuild/2013102859514.html" rel="nofollow" target="_blank"&gt;控制&lt;/a&gt;的并不好，这时候，我们可以通过以下命令解决：&lt;/p&gt;    &lt;div&gt;      &lt;pre&gt;-maxrate biterate -minrate biterate -bf1-b_strategy0&lt;/pre&gt;&lt;/div&gt;    &lt;p&gt;      其中 -maxrate、-minrate为      &lt;a href="http://www.68idc.cn/help/interjs/ask/20141220146960.html" rel="nofollow" target="_blank"&gt;设置&lt;/a&gt;最小最大比特率，-bf为设置B帧数目，其实就是设置      &lt;a href="http://www.68idc.cn/help/jiabenmake/qita/20141217144660.html" rel="nofollow" target="_blank"&gt;编码&lt;/a&gt;是B、P、I帧的结构，我这里设置的为IPBPBP结构，-b_strategy这个命令是为了自适应的添加B帧数目，ffmpeg编码器会根据视频的应用场景，自适应的添加B帧，通过设置-b_strategy 0，，将这个功能关闭，那么就会根据你的设置要求进行编码。除此之外，还可以使用-pass，进行2次      &lt;a href="http://www.68idc.cn/help/jiabenmake/qita/20141202137140.html" rel="nofollow" target="_blank"&gt;码率&lt;/a&gt;控制，编出来的视频效果更好；下面我介绍-pass的使用方法：&lt;/p&gt;    &lt;p&gt;      (1) -pass 1 -passlogfile ffmpeg2pass 第一步先编一次，生成 ffmpeg2pass 文件&lt;/p&gt;    &lt;p&gt;      (2) -pass 2 -passlogfile ffmpeg2pass 第二次会根据第一次生成的ffmpeg2pass 文件，再进行码率控制。&lt;/p&gt;    &lt;p&gt;2.       &lt;a href="http://www.68idc.cn/help/server/2014040682969.html" rel="nofollow" target="_blank"&gt;如何&lt;/a&gt;设置视频      &lt;a href="http://www.68idc.cn/help/cms/ecshop/20150109163785.html" rel="nofollow" target="_blank"&gt;关键&lt;/a&gt;帧I帧间隔问题&lt;/p&gt;    &lt;p&gt;       刚开始我只使用-g命令，设置GOP长度，编码后，发现I帧间隔长度并不是我想要的，后来我通过以下命令问题解决了：&lt;/p&gt;    &lt;div&gt;      &lt;pre&gt;-keyint_min60-g60-sc_threshold0&lt;/pre&gt;&lt;/div&gt;    &lt;p&gt;     其中-keyint_min为最小      &lt;a href="http://www.68idc.cn/help/cms/ecshop/20150109163785.html" rel="nofollow" target="_blank"&gt;关键&lt;/a&gt;帧间隔，我这里设置为60帧；-sc_threshold这个命令会根据视频的运动场景，自动为你添加额外的I帧，所以会导致你编出来的视频关键帧间隔不是你设置的长度，这是只要将它设为0，问题就得到解决了！！&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;        3.在用ffmpeg转换视频到flv过程中，需要设置关键帧的间隔，以便在播放过程中实现精确定位。在网上查找了不少，最后发现这个指令有效：&lt;/p&gt;    &lt;div&gt;      &lt;pre&gt;-g1-keyint_min2&lt;/pre&gt;&lt;/div&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;// 将关键帧帧间隔设置为2s&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;./ffmpeg -i ~/Documents/video/fc.mkv -acodec libfdk_aac -vcodec libx264 -keyint_min 50 -g 50 -sc_threshold 0 fc_transcode.mkv&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt; &lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;如果使用OBS推流，可以在设置中设置关键帧间隔&lt;/strong&gt;&lt;/p&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/59609-ffmpeg-%E7%BC%96%E7%A0%81-%E6%81%92%E5%AE%9A</guid>
      <pubDate>Tue, 21 May 2019 14:05:51 CST</pubDate>
    </item>
    <item>
      <title>Uber先进技术集团首席科学家：自动驾驶汽车大规模普及还需要很长时间</title>
      <link>https://itindex.net/detail/59435-uber-%E6%8A%80%E6%9C%AF-%E9%9B%86%E5%9B%A2</link>
      <description>&lt;p&gt;【TechWeb】4月9日消息，据国外媒体报道，自动驾驶是近几年汽车行业的一大热点，谷歌、苹果、特斯拉、通用等众多企业都在这方面有布局，其中也包括了以打车服务出名的Uber，但Uber自动驾驶方面的高管近日表示，自动驾驶汽车大规模普及还需要很长的时间。&lt;/p&gt;
 &lt;p&gt;认为自动驾驶汽车还需要很长的时间才能普及的，是Uber先进技术集团的首席科学家拉克尔·乌尔塔森（Raquel Urtasun），先进技术集团是Uber进行自动驾驶等先进技术研发的部门，拉克尔·乌尔塔森负责领导这一集团在加拿大多伦多的部门。&lt;/p&gt;
 &lt;p&gt;拉克尔·乌尔塔森是当地时间周一在纽约的一次会议上，表示自动驾驶汽车大规模普及还需要很长的时间的。&lt;/p&gt;
 &lt;p&gt;乌尔塔森在会议上表示，自动驾驶汽车正在走入我们的生活，但问题是时间还不清晰，自动驾驶汽车大规模上路还需要很长的时间。&lt;/p&gt;
 &lt;p&gt;目前有多家公司在进行自动驾驶汽车方面的研发，谷歌兄弟公司Waymo被认为是在这一技术上走在行业前列的企业，但其CEO约翰·科拉菲克(John Krafcik)去年7月份在美国全国州长大会上也表示，虽然他们在自动驾驶技术方面取得了进步，但自动驾驶汽车普及所需要的时间，仍会长的超乎想象。（辣椒客）&lt;/p&gt; &lt;p&gt;  &lt;img alt="&amp;#25991;&amp;#31456;" border="0" src="http://s1.techweb.com.cn/static/img/20180614.png"&gt;&lt;/img&gt;&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>资讯编译</category>
      <guid isPermaLink="true">https://itindex.net/detail/59435-uber-%E6%8A%80%E6%9C%AF-%E9%9B%86%E5%9B%A2</guid>
      <pubDate>Tue, 09 Apr 2019 18:18:00 CST</pubDate>
    </item>
    <item>
      <title>用3天时间设计一款完整的APP作品</title>
      <link>https://itindex.net/detail/59339-%E6%97%B6%E9%97%B4-%E8%AE%BE%E8%AE%A1-%E5%AE%8C%E6%95%B4</link>
      <description>&lt;p align="center"&gt;  &lt;a href="http://www.shejidaren.com/ui-case-study-designing-a-food-app-in-3-days.html" target="_blank"&gt;   &lt;img alt="" src="http://images.shejidaren.com/wp-content/uploads/2019/03/065732fwX.jpg" title=""&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;很多人说看过很多设计道理却依然做不好设计，还不如直接看设计案例来的简单粗暴。今天为大家找到一篇实战好文，一个人，3天时间，打造一款APP全流程设计，学起来吧！

 &lt;img src="http://images.shejidaren.com/wp-content/uploads/2019/03/065732fwX.jpg"&gt;&lt;/img&gt;


几周前，我接到一个设计需求是为食品行业设计一套解决方案，甲方给出的时间是必须在3天内完成。乍一听，可能会觉得这几乎是不可能的，但是如果你熟悉“GV Design Sprint ” &lt;small&gt;（译者注：GV  Design Sprint就是一个专业的设计流程方案，想了解的朋友请见https://designsprintkit.withgoogle.com/introduction/overview），&lt;/small&gt;那么就会明白，这实际上是可以做到的。

我发现这是一个非常好的机会，可以在这么短的时间内实践一套设计方法。在这篇文章中，我将逐一向大家分解我的设计过程和每天的设计进展。
 &lt;h3&gt;项目背景&lt;/h3&gt;
Common Food是一个使用社区支持农业(CSA)出售水果蔬菜的农场。人们需要在年初提前预定农场一年的收成，然后在生长季节里，社区成员每周都会收到一箱食品。 &lt;small&gt;（译者注：CSA的运作过程通常由认同相同理念的社区支持者（个人或单位）采用共同购买的模式，预先支付一笔费用给当地的小农户预约一季或一年的收成，农民收成后，再根据实际产出的多寡分配给会员，如此达到结合社区力量支持在地农民采用有机农耕的积极意义。——百度百科）&lt;/small&gt;
 &lt;h3&gt;设计挑战&lt;/h3&gt;
设计一款应用，帮助普通农场每周能向会员们卖出更多的农副产品。帮助他们个性化的推荐食品方案，以满足会员们不同的知识水平，兴趣和饮食限制。
 &lt;h2&gt;第一天：资料收集&lt;/h2&gt;
 &lt;h3&gt;研究方法&lt;/h3&gt;
研究阶段往往会占用一些时间，所以尽可能的利用手头现有信息是节省时间的好办法。市场环境二手资料，竞争对手，用户调研或者直接去App Store中收集用户评论，我需要尽早了解清楚用户的思维模式。
 &lt;h3&gt;用户调研&lt;/h3&gt;
Common Food 公司会在每个季度对会员们进行回访，以便能更好的了解他们的喜好，以下是今年的一些反馈：

 &lt;blockquote&gt;“我喜欢烹饪和做罐头。我可以多买一些西红柿吗？”

“大头菜我不喜欢吃，我把它都扔掉了，不过生菜确实很好吃。”

“我儿子对花椰菜过敏，所以我们从不吃花椰菜。另外我喜欢吃蒜蓉，但不知道怎么做。”

“我希望我能有更多的大头菜，然后用来做泡菜。”

“ 我觉得我们应该需要更多的CSAs，继续扩大农场的规模。我现在看到了很多转基因的怪物农作物，希望有渠道能及时发布最新消息。”

“我们能不能弄到更多的甜菜？”

“我从来没听过白菜，它看起来很漂亮，但是我不知道怎么做这道菜。”&lt;/blockquote&gt;

一份来自Field Agent的最新报告发现，不管是在计划内还是计划外购物的网购者来说，农产品都是一个很受欢迎的类别。65%的受访消费者表示，他们购买的是新鲜农产品，与冷冻乳制品并列第一。31%的人说他们一时冲动买了新鲜水果和蔬菜，超过了零食（23%）和糖果（14%）。然而，39%的网购者并不会在网上购买新鲜农产品，因为他们更喜欢去菜市场。
 &lt;h3&gt;市场调研&lt;/h3&gt;
市场研究公司Mintel发现，尽管消费者越来越多的转向网上购物，但只有十分之一的美国人会通过电商购买新鲜的农产品，肉类，家禽和鱼类。该研究建议，为了打消消费者的顾虑，提高销售额，零售商应该多提倡节约成本，并提供更全面的产品信息，以建立信任，提升价值吸引力。

研究表明，亚马逊的“购物车和收藏”等功能对于忙碌的消费者来说也是一个很有吸引力的点，特别是女性（48%）比男性（37%）更有可能在线购买，做好功能体验很重要。
 &lt;h3&gt;头脑风暴&lt;/h3&gt;

 &lt;img src="http://images.shejidaren.com/wp-content/uploads/2019/03/065732Kcq.jpg"&gt;&lt;/img&gt;


脑暴出的问题

1、我们如何收集用户需求数据来减少浪费和分配食品？

2、我们如何通过季节性食物和促销活动来增加销售业绩？

3、我们如何帮助会员根据他们的饮食偏好发掘额外的食物需求？
 &lt;h3&gt;用户画像&lt;/h3&gt;
用户调查的结果提供了足够的信息来创建用户画像。我选择创建与年龄和性别都无关的角色，以便能够将更加聚焦在如何平等的实现用户目标上。

 &lt;img src="http://images.shejidaren.com/wp-content/uploads/2019/03/065733NqL.jpg"&gt;&lt;/img&gt;


 &lt;p&gt;用户画像&lt;/p&gt;
 &lt;h3&gt;竞品分析&lt;/h3&gt;
当我准备进入草图阶段时，我将会研究在相关行业或竞争对手那里类似的问题和解决方案，以确定最佳方案。

 &lt;strong&gt;盒子大小和内容分类&lt;/strong&gt;

FarmBox Direct和Farm Fresh to you两款产品分别提供不同的盒子大小和产品选项。（译者注：为什么会定义盒子大小，这是因为他们每周会发放给会员的食品是以盒子来计量的。）

 &lt;img src="http://images.shejidaren.com/wp-content/uploads/2019/03/0657337hv.jpg"&gt;&lt;/img&gt;


 &lt;strong&gt;自定义盒子装的食品种类&lt;/strong&gt;

FarmFresh to You让你定制你的盒子，添加或删除农产品和设置数量。但你必须充会员才能定制它，与此同时，你也可以为排除项创建项目列表。

 &lt;img src="http://images.shejidaren.com/wp-content/uploads/2019/03/065733zNs.jpg"&gt;&lt;/img&gt;


 &lt;p&gt;FarmFresh to You&lt;/p&gt;

 &lt;strong&gt;真实的食品照片&lt;/strong&gt;

Farmstead 允许添加任意数量的购物清单，你可以添加，删除和浏览，都没问题之后再来结账。Farmstead提供真实新鲜的产品照片，不像其他竞争对手使用库存的照片。

 &lt;img src="http://images.shejidaren.com/wp-content/uploads/2019/03/065733y8m.jpg"&gt;&lt;/img&gt;


 &lt;h3&gt;方案思考&lt;/h3&gt;
我的解决方案是设计一个APP，从会员那里收集数据，比如家庭规模、饮食偏好等等，让会员们可以轻松定制季节性食谱。利用人工智能了解会员的饮食习惯，提供食谱，饮食建议和运营一些有针对性的促销活动，取得双方共赢。这些数据可以帮助我们从会员那里得到反馈，然后就可以知道他们在哪个季节可能会重新订购哪种类型的产品或组合，以及他们实际上最喜欢什么产品。

 &lt;h2&gt;第二天：草图和设计&lt;/h2&gt;
我喜欢在自己感觉“明白了”之后开始画草图，我会把自己的想法都画出来，然后再把自己觉得不好的想法剔除掉，保留自己觉得最好的效果。我会通过绘制用户旅程地图来定义用户任务和目标。

 &lt;img src="http://images.shejidaren.com/wp-content/uploads/2019/03/06573311g.jpg"&gt;&lt;/img&gt;


 &lt;p&gt;用户旅程地图&lt;/p&gt;

解决了用户角色，竞品分析和草图方案，接下来，我就开始为注册了这些服务的会员们设计流程。一旦他们通过APP首次下单，我们的数据库中就有了用户的日程安排、发货和账单信息。一旦确认了这些流程信息，我就准备开始画线框图。

 &lt;img src="http://images.shejidaren.com/wp-content/uploads/2019/03/065734pUx.jpg"&gt;&lt;/img&gt;


 &lt;strong&gt;线框图&lt;/strong&gt;

线框图是APP的骨架，这让我在开始做视觉设计之前，能够专注于关键功能、元素和交互。我选择了高保真线框图，这样我只需要在一些色彩，配图，和图标上进一步思考视觉呈现。

 &lt;img src="http://images.shejidaren.com/wp-content/uploads/2019/03/065734H8v.jpg"&gt;&lt;/img&gt;


 &lt;strong&gt;风格板&lt;/strong&gt;

在画好线框图之后，我会找相同行业内的APP视觉做一个风格板来作为设计参考。

 &lt;img src="http://images.shejidaren.com/wp-content/uploads/2019/03/065734yga.jpg"&gt;&lt;/img&gt;


 &lt;strong&gt;配色方案&lt;/strong&gt;

我选择了鲜红色作为主色。鲜艳的颜色会让人胃口大开，红色也会引发购物欲望（促销、清仓、热闹等氛围）。

 &lt;img src="http://images.shejidaren.com/wp-content/uploads/2019/03/065734gyD.jpg"&gt;&lt;/img&gt;


 &lt;strong&gt;字体选择&lt;/strong&gt;

 &lt;img src="http://images.shejidaren.com/wp-content/uploads/2019/03/0657342T9.jpg"&gt;&lt;/img&gt;


 &lt;strong&gt;图标设计&lt;/strong&gt;

当说到图标和节省时间这个话题时，我不建议把它们都画出来。尽管我很喜欢自己画图标，但是这次时间上并不允许。我推荐一些图标库，比如Material Design icons（https://material.io/tools/icons/），或者我这个应用用到的一个图标库The Noun Project（https://thenounproject.com/#）。在利用这些图标库时，请注意购买版权，如果不想付费，也必须注明图标来源，定稿后有时间再来重新画。

 &lt;img src="http://images.shejidaren.com/wp-content/uploads/2019/03/065734SKf.jpg"&gt;&lt;/img&gt;


 &lt;strong&gt;Logo设计&lt;/strong&gt;

我期望这个APP能有一个清晰易懂的名字，同时也希望能有一个与名字非常匹配的Logo。在思考了很多名字之后，我觉得“FameCrate(农场条板箱)”这个名字非常的适合，通过不断迭代，优化，最终得到了下面这个Logo。

 &lt;img src="http://images.shejidaren.com/wp-content/uploads/2019/03/06573562A.jpg"&gt;&lt;/img&gt;


 &lt;strong&gt;视觉设计&lt;/strong&gt;

我喜欢尝试不同的风格和设计变化，我对第一稿不满意，因为缺乏整体上的一致性，然后不断优化，直到最终全局页面都能做到统一协调并符合自己的设计预期为止。

 &lt;img src="http://images.shejidaren.com/wp-content/uploads/2019/03/065735hv0.jpg"&gt;&lt;/img&gt;


 &lt;h2&gt;第三天：交互原型&lt;/h2&gt;
到了第三天，我完成了最后的视觉设计，便开始做可交互原型。我等到这个阶段才开始做这事，主要是因为如果在线框阶段就开始做原型的话，尽管它们是高保真的（我经常这么做），但会花很多时间在交互界面上。对于这个项目，我在设计完成后再来添加交互动作，其实是为了节省时间。

 &lt;strong&gt;最终设计&lt;/strong&gt;

 &lt;img src="http://images.shejidaren.com/wp-content/uploads/2019/03/065735zDc.jpg"&gt;&lt;/img&gt;


 &lt;img src="http://images.shejidaren.com/wp-content/uploads/2019/03/065736E8G.jpg"&gt;&lt;/img&gt;


 &lt;strong&gt;自动动画&lt;/strong&gt;

我会用Adobe XD的自动动画（https://helpx.adobe.com/xd/help/create-prototypes-using-auto-animate.html）功能来做原型设计，这是节省交互动画制作时间的好办法，而不是在After Effects上花费几个小时来做这种效果。

 &lt;img src="http://images.shejidaren.com/wp-content/uploads/2019/03/065736WNL.jpg"&gt;&lt;/img&gt;


下一步

 &lt;strong&gt;可用性测试&lt;/strong&gt;

找一些真实用户来做这项测试。我个人最喜欢用Maze.Design（https://maze.design/）这款工具来记录测试结果，它易于使用，并能提供有关原型的全面数据分析。

 &lt;strong&gt;不断迭代&lt;/strong&gt;

通过可用性测试的一些结论，在设计上进行迭代，改进用户流程。

 &lt;strong&gt;总结&lt;/strong&gt;

在有限的时间内，你绝对不能让自己分心，必须专注于这个项目并管理好自己的时间。我会给自己进行计时，并在每一个步骤上给自己设定时间限制。我试着让APP用起来尽可能的简单，从草图和用户旅程地图就要开始思考，最后这一切才能水到渠成。

 &lt;strong&gt;感悟&lt;/strong&gt;

永远相信你的直觉，不要害怕设计修改。如果你把我的线框图和最终的设计效果图进行比较，会发现我做了很多的选择来改进最终的设计。另外，也可以使用你自己熟悉的软件，或者你自己知道的更快速的方法，而不要太花费时间来确定是不是跟我完全一样。

 &lt;small&gt;原文：https://uxdesign.cc/ux-ui-case-study-designing-a-food-app-in-3-days-1e2856680205
作者：Paola Ascanio
译者：彩云Sky(微信号:caiyunyisheji)
本文翻译已获得作者的正式授权&lt;/small&gt; &lt;hr&gt;&lt;/hr&gt; &lt;br /&gt; &lt;br /&gt;(ノ◕‿◕)ノ*:･ﾟ✧  &lt;a href="http://hao.shejidaren.com" target="_blank" title="&amp;#35774;&amp;#35745;&amp;#23548;&amp;#33322;"&gt;查看最受欢迎 301 个设计网站&lt;/a&gt; *:･ﾟ✧ヽ(◕‿◕ヽ)  &lt;br /&gt; &lt;br /&gt; &lt;a href="http://hao.shejidaren.com/sheji-qq-qun.html" target="_blank" title="UI&amp;#35774;&amp;#35745;QQ&amp;#32676;"&gt;UI设计QQ群&lt;/a&gt;  ¦  &lt;a href="http://www.shejidaren.com/feed" target="_blank" title="RSS&amp;#35746;&amp;#38405;"&gt;RSS订阅&lt;/a&gt; ¦  &lt;a href="http://weibo.com/shejidaren888" target="_blank" title="&amp;#26032;&amp;#28010;&amp;#24494;&amp;#21338;"&gt;新浪微博&lt;/a&gt; ¦  &lt;a href="http://www.shejidaren.com/ui-case-study-designing-a-food-app-in-3-days.html" target="_blank" title="&amp;#29992;3&amp;#22825;&amp;#26102;&amp;#38388;&amp;#35774;&amp;#35745;&amp;#19968;&amp;#27454;&amp;#23436;&amp;#25972;&amp;#30340;APP&amp;#20316;&amp;#21697;"&gt;本文链接&lt;/a&gt; ¦  &lt;a href="http://www.shejidaren.com/ui-case-study-designing-a-food-app-in-3-days.html#respond" target="_blank" title="&amp;#29992;3&amp;#22825;&amp;#26102;&amp;#38388;&amp;#35774;&amp;#35745;&amp;#19968;&amp;#27454;&amp;#23436;&amp;#25972;&amp;#30340;APP&amp;#20316;&amp;#21697;&amp;#30340;&amp;#35780;&amp;#35770;"&gt;添加评论&lt;/a&gt;  &lt;br /&gt; &lt;img src="http://ww4.sinaimg.cn/large/6857cd42gw1f2n261vbdfj20cb04u0tb.jpg"&gt;&lt;/img&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>交互设计 UI UI设计 UX 设计理论</category>
      <guid isPermaLink="true">https://itindex.net/detail/59339-%E6%97%B6%E9%97%B4-%E8%AE%BE%E8%AE%A1-%E5%AE%8C%E6%95%B4</guid>
      <pubDate>Wed, 06 Mar 2019 08:00:14 CST</pubDate>
    </item>
    <item>
      <title>使用树莓派实现24小时不间断直播 - EdmondFrank's 时光足迹</title>
      <link>https://itindex.net/detail/59198-%E6%A0%91%E8%8E%93%E6%B4%BE-%E7%9B%B4%E6%92%AD-edmondfrank</link>
      <description>&lt;h2&gt;开始&lt;/h2&gt; &lt;p&gt;多余的话就不多说了，今天本文为大家介绍两种使用树莓派来做直播服务器的方法。&lt;/p&gt; &lt;h2&gt;方案一 ffmpeg + ffserver搭建流媒体服务器&lt;/h2&gt; &lt;p&gt;  &lt;strong&gt;首先&lt;/strong&gt;   &lt;br /&gt;我们用到的工具有：&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;硬件方面：&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;树莓派主板一块&lt;/li&gt;  &lt;li&gt;兼容树莓派的USB摄像头一个&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;strong&gt;软件方面：&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;ffmpeg，负责媒体文件的转码工作，把你服务器上的源媒体文件转成要发出去的流媒体文件。&lt;/li&gt;  &lt;li&gt;ffserver，负责响应客户端的流媒体请求，把流媒体数据发送给客户端，相当与一个小型的服务端。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;具体的工作方式就如下图所示：   &lt;br /&gt;  &lt;img alt="" src="https://ws1.sinaimg.cn/large/a3d23450gy1fodzrw4b72j20iu0buq52.jpg" title=""&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;多个输入源被“喂”到广播服务器，这些多媒体内容就会分发到多个客户端。上图的目的是显示地表明你的流系统能够被分成多个块部署到网络上，允许你广播不同的在线内容，而不需要改变流媒体系统的结构。&lt;/p&gt; &lt;h3&gt;配置&lt;/h3&gt; &lt;p&gt;无论是树莓派官方摄像头模块还是其他兼容的USB摄像头，连接好摄像头之后，运行命令去启用摄像头：&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;sudo raspi-config&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;  &lt;strong&gt;ffserver.conf&lt;/strong&gt;，ffserver启动时的配置文件，在这个文件中主要是对网络协议，缓存文件feed1.ffm（见下述）和要发送的流媒体文件的格式参数做具体的设定。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;feed1.ffm&lt;/strong&gt;，可以看成是一个流媒体数据的缓存文件，ffmpeg把转码好的数据发送给ffserver，如果没有客户端连接请求，ffserver把数据缓存到该文件中。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;下面就是一个ffserver.conf的一个例子&lt;/strong&gt;：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;Port 8090                      # Port to bind the server to
BindAddress 0.0.0.0
MaxHTTPConnections 2000
MaxClients 1000
MaxBandwidth 10000             # Maximum bandwidth per client
                               # set this high enough to exceed stream bitrate
CustomLog -
NoDaemon                       # Remove this if you want FFserver to daemonize after start

&amp;lt;Feed feed1.ffm&amp;gt;               # This is the input feed where FFmpeg will send
   File ./feed1.ffm            # video stream.
   FileMaxSize 64M              # Maximum file size for buffering video
   ACL allow 127.0.0.1         # Allowed IPs
&amp;lt;/Feed&amp;gt;

&amp;lt;Stream test.webm&amp;gt;              # Output stream URL definition
   Feed feed1.ffm              # Feed from which to receive video
   Format webm

   # Audio settings
   AudioCodec vorbis
   AudioBitRate 64             # Audio bitrate

   # Video settings
   VideoCodec libvpx
   VideoSize 720x576           # Video resolution
   VideoFrameRate 25           # Video FPS
   AVOptionVideo flags +global_header  # Parameters passed to encoder
                                       # (same as ffmpeg command-line parameters)
   AVOptionVideo cpu-used 0
   AVOptionVideo qmin 10
   AVOptionVideo qmax 42
   AVOptionVideo quality good
   AVOptionAudio flags +global_header
   PreRoll 15
   StartSendOnKey
   VideoBitRate 400            # Video bitrate
&amp;lt;/Stream&amp;gt;

&amp;lt;Stream status.html&amp;gt;            # Server status URL
   Format status
   # Only allow local people to get the status
   ACL allow localhost
   ACL allow 192.168.0.0 192.168.255.255
&amp;lt;/Stream&amp;gt;

&amp;lt;Redirect index.html&amp;gt;    # Just an URL redirect for index
   # Redirect index.html to the appropriate site
   URL http://www.ffmpeg.org/
&amp;lt;/Redirect&amp;gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;ffserver启动时默认查看 /etc/ffserver.conf 配置文件，你可以通过-f选项控制查阅的配置文件。&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;ffserver -f ffserver.conf&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;运行结果如下所示的话，那么ffserver就算是启动成功了。&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="https://ws1.sinaimg.cn/large/a3d23450gy1fodzzq0svjj20xm08jgmv.jpg" title=""&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;打开  &lt;a href="http://localhost:8090/status.html"&gt;http://localhost:8090/status.html&lt;/a&gt;可以看到当前server中各个流的状态。&lt;/p&gt; &lt;h3&gt;接入视频流&lt;/h3&gt; &lt;p&gt;ffserver启动之后，就可以向   &lt;br /&gt;  &lt;a href="http://localhost:8090/feed1.ffm"&gt;http://localhost:8090/feed1.ffm&lt;/a&gt;接入视频流。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;注意&lt;/strong&gt;，这里不需要指定编码格式，FFserver会重新编码。&lt;/p&gt; &lt;p&gt;视频流的来源可以是文件、摄像头或者录制屏幕。&lt;/p&gt; &lt;h3&gt;接入视频文件&lt;/h3&gt; &lt;pre&gt;  &lt;code&gt;ffmpeg -i testvideo.mp4 http://localhost:8090/feed1.ffm&lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;接入录制屏幕&lt;/h3&gt; &lt;pre&gt;  &lt;code&gt;ffmpeg -f x11grab -r 25 -s 640x512 -i :0.0 -f alsa -i pulse http://localhost:8090/feed1.ffm
&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这里有两个-f，第一个指的是视频流，第二个指的是音频流。视频流是抓取屏幕形成视频，-r设置帧率为25帧/s，-s设置抓取图像大小为640x512，-i设置录制视频的初始坐标。音频流设置为alsa(Advanced Linux Sound Architecture)，从Linux系统中获取音频。这其中这样ffmpeg可以录制屏幕feed到feed1.ffm中。&lt;/p&gt; &lt;h3&gt;接入摄像头直播&lt;/h3&gt; &lt;pre&gt;  &lt;code&gt;ffmpeg -f video4linux2 -s 640x480 -r 25 -i /dev/video0 -f alsa -i pulse http://localhost:8090/feed1.ffm&lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;方案二 avconv 和 GStreamer 用于采集摄像头捕获的视频流并推送到 RTMP 服务&lt;/h2&gt; &lt;p&gt;  &lt;strong&gt;首先&lt;/strong&gt;   &lt;br /&gt;我们用到的工具有：&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;硬件方面：&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;树莓派主板一块&lt;/li&gt;  &lt;li&gt;兼容树莓派的USB摄像头一个&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;strong&gt;软件方面：&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;avconv 和 GStreamer 用于采集摄像头捕获的视频流并推送到 RTMP 服务&lt;/li&gt;  &lt;li&gt;NGINX 和 RTMP 模块，用于接收视频流，同时提供视频发布功能&lt;/li&gt;&lt;/ul&gt; &lt;h3&gt;安装＆配置&lt;/h3&gt; &lt;p&gt;因为这里我们要用到nginx的rtmp模块作为服务端，而系统自带的apt安装的nginx是没有这个模块的，所以我们需要先安装后移除nginx然后再手动编译（安装是为了下载好相关依赖）。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;sudo apt-get update
#安装 nginx
sudo apt-get -y install nginx
#移除 nginx
sudo apt-get -y remove nginx
sudo apt-get clean
#清空 nginx 的配置文件
sudo rm -rf /etc/nginx/*
#安装编译用的模块
sudo apt-get install -y curl build-essential libpcre3 libpcre3-dev libpcre++-dev zlib1g-dev libcurl4-openssl-dev libssl-dev
#创建存放网页的目录给 nginx 使用
sudo mkdir -p /var/www
#创建编译用的目录
mkdir -p ~/nginx_src
cd ~/nginx_src
#下载 nginx 源码包
wget http://nginx.org/download/nginx-1.11.8.tar.gz
#下载 nginx-rtmp-module 源码包
wget https://github.com/arut/nginx-rtmp-module/archive/master.zip
tar -zxvf nginx-1.11.8.tar.gz
unzip master.zip
cd nginx-1.11.8
#设定编译参数
./configure --prefix=/var/www --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --pid-path=/var/run/nginx.pid --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --with-http_ssl_module --without-http_proxy_module --add-module=/home/pi/nginx_src/nginx-rtmp-module-master
#开始编译安装
make
sudo make install&lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;配置 nginx&lt;/h3&gt; &lt;blockquote&gt;  &lt;p&gt;sudo gedit /etc/nginx/nginx.conf&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;在末尾添加&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;rtmp {
    server {
        listen 1935;
        chunk_size 4096;
        application live {
            live on;
            record off;
        }
    }
}&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;重启 nginx 服务。&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;sudo service nginx start&lt;/p&gt;&lt;/blockquote&gt; &lt;h3&gt;安装 avconv 和 GStreamer&lt;/h3&gt; &lt;pre&gt;  &lt;code&gt;sudo apt-get update
sudo apt-get install libav-tools
#安装 GStreamer
sudo apt-get install gstreamer1.0-tools
#安装 GStreamer 扩展组件
sudo apt-get  install libgstreamer1.0-0 libgstreamer1.0-0-dbg libgstreamer1.0-dev liborc-0.4-0 liborc-0.4-0-dbg liborc-0.4-dev liborc-0.4-doc gir1.2-gst-plugins-base-1.0 gir1.2-gstreamer-1.0 gstreamer1.0-alsa gstreamer1.0-doc gstreamer1.0-omx gstreamer1.0-plugins-bad gstreamer1.0-plugins-bad-dbg gstreamer1.0-plugins-bad-doc gstreamer1.0-plugins-base gstreamer1.0-plugins-base-apps gstreamer1.0-plugins-base-dbg gstreamer1.0-plugins-base-doc gstreamer1.0-plugins-good gstreamer1.0-plugins-good-dbg gstreamer1.0-plugins-good-doc gstreamer1.0-plugins-ugly gstreamer1.0-plugins-ugly-dbg gstreamer1.0-plugins-ugly-doc gstreamer1.0-pulseaudio gstreamer1.0-tools gstreamer1.0-x libgstreamer-plugins-bad1.0-0 libgstreamer-plugins-bad1.0-dev libgstreamer-plugins-base1.0-0 libgstreamer-plugins-base1.0-dev
&lt;/code&gt;&lt;/pre&gt; &lt;h3&gt;采集与呈现视频流&lt;/h3&gt; &lt;pre&gt;  &lt;code&gt;gst-launch-1.0 -v v4l2src device=/dev/video0 ! &amp;apos;video/x-raw, width=1024, height=768, framerate=30/1&amp;apos; ! queue ! videoconvert ! omxh264enc ! h264parse ! flvmux ! rtmpsink location=&amp;apos;rtmp://树莓派的IP地址/live live=1&amp;apos; &amp;amp;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;采用以上命令就可以在后台采集ＵＳＢ摄像头拍摄的直播内容并推送到ｒｔｍｐ服务端上了。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;呈现直播视频画面&lt;/strong&gt;：&lt;/p&gt; &lt;p&gt;1、使用 RTMP 播放器播放视频流   &lt;br /&gt;例如 VLC 等播放器（桌面版和手机版均有）支持 RTMP 视频流播放，填入 rtmp://树莓派的IP地址/live 即可播放。不过这个软件有数十秒的缓冲延迟，需要设定缓冲时间来缩短延迟。&lt;/p&gt; &lt;p&gt;２、推送至斗鱼直播平台观看   &lt;br /&gt;你可能注意到了 GStreamer 这个命令中有 location 这个参数。这个参数是设定采集到的视频流推向哪里，通过设定这个参数可以将视频流推向任何支持 RTMP 协议的服务器。&lt;/p&gt; &lt;p&gt;斗鱼平台同样采用了 RTMP 协议传输直播视频，首先获取斗鱼的 RTMP 推流地址。开启了直播室之后可以获得推流码。注意，斗鱼的推流码是有时限的，取到推流码需要尽快使用以免过期。&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/59198-%E6%A0%91%E8%8E%93%E6%B4%BE-%E7%9B%B4%E6%92%AD-edmondfrank</guid>
      <pubDate>Thu, 10 Jan 2019 10:48:02 CST</pubDate>
    </item>
    <item>
      <title>攻略：如何用一天时间做一个开源版的Nest_36氪</title>
      <link>https://itindex.net/detail/58985-%E6%94%BB%E7%95%A5-%E4%B8%80%E5%A4%A9-%E6%97%B6%E9%97%B4</link>
      <description>&lt;div&gt;    &lt;p&gt;上周科技界最大的新闻恐怕就是谷歌花了32亿美刀      &lt;a href="http://www.36kr.com/p/209102.html"&gt;收购了&lt;/a&gt;大名鼎鼎的智能设备公司      &lt;a href="http://www.36kr.com/p/203193.html"&gt;Nest&lt;/a&gt;。它的两款产品（智能温控器和烟雾探测器）以优秀的工业设计和颠覆性的功能为人称道。更重要的是，Nest向我们展现了什么才是用户想要的“物联网”产品。&lt;/p&gt;    &lt;p&gt;不过，Nest固然好，在中国想要得到它可不容易。它在淘宝上的价格高达1200+人名币。不过不用担心，在这个创客横行的时代，神马硬件都是浮云。买不到？太贵？不用担心，我们来自己动手做一个。&lt;/p&gt;    &lt;p&gt;来自于开源硬件领域的      &lt;a href="https://www.spark.io/"&gt;Spark&lt;/a&gt;已经利用自家的产品把这事搞定了，而且只是3个工程师花了一天的时间，成本也不过几十美金。让我们一起来看看他们是怎么做到的。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;1. 硬件部分&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;先来看看我们都需要哪些零件：&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;主控板，        &lt;a href="https://www.spark.io/#"&gt;Spark Core&lt;/a&gt;；&lt;/li&gt;      &lt;li&gt;温度显示屏，        &lt;a href="http://www.adafruit.com/products/870"&gt;Adafruit 8x8 LED矩阵&lt;/a&gt;，接口是通用I2C总线；&lt;/li&gt;      &lt;li&gt;主传感器是        &lt;a href="http://www.digikey.com/product-detail/en/HIH6131-021-001/480-3652-6-ND/2704706"&gt;Honeywell Humidlcon&lt;/a&gt;的温度湿度传感器，和显示屏共享I2C总线；&lt;/li&gt;      &lt;li&gt;我们可以暂时利用LED灯来代替我们的空调；&lt;/li&gt;      &lt;li&gt;你需要让设备知道什么时候有人在家，我们选用        &lt;a href="http://pewa.panasonic.com/assets/pcsd/catalog/napion-catalog.pdf"&gt;Panasonic PIR motion detector&lt;/a&gt;；&lt;/li&gt;      &lt;li&gt;外壳；&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;（注：这些零件除了Spark Core之外，你在淘宝上都可以轻松找到，价格也会便宜不少。Spark Core可以考虑用Arduino + Wi-Fi模块来代替，不过程序部分可能会麻烦不少。）&lt;/p&gt;    &lt;p&gt;搞定上述零件（除了外壳）后，我们需要把这些东西都用面包板连接起来，这个过程需要大概1个小时。使用面包板能够帮你快速制作出第一版的产品原型，而且便于修改。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="http://a.36krcnd.com/photo/2014/8ea20897525ad16c6b1d38d28121bb75.png!heading"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;下一步，是给我们的“Nest”做一个外壳。官方外壳的材质采用的是玻璃和铝的材质，这两种材料质感不错但是不方便在家里加工。我们选用木材和亚克力板来代替。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="http://a.36krcnd.com/photo/2014/bbfc03a6f7569ba8d8df8e8aaa40d929.png!heading"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;首先，我们用CNC来加工两块木头，一个作为固定的基座，另一个则是可以自由旋转的调温装置，顺时针转调高温度。接下来，用激光切割机加工三块亚克力板，一块是正面显示温度的面板，一块是背面固定在墙面上的安装板，第三块连接在可以旋转的那块木头上，使其成为一个电位器。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="http://a.36krcnd.com/photo/2014/47e03dc2510eb6d9c574eec0edbed85f.png!heading"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;一旦我们完成了外壳，我们就需要把所有的面包板零件都塞进去，调整一下零件的布局，用焊接的方式固定电路。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="http://a.36krcnd.com/photo/2014/329f3f58dc0ee24ecb94a349fbe9569a.png!heading"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;2. 软件部分&lt;/strong&gt;（Spark团队已经将所有代码上传至      &lt;a href="http://www.github.com/spark/thermostat"&gt;Github&lt;/a&gt;）&lt;/p&gt;    &lt;p&gt;调温器的程序（又称固件）需要实现从传感器读取数据、控制继电器和在屏幕上显示温度等等功能。同时，我们还需要一个无线信号接收器，以便远程从手机或电脑上来控制温度。 当然，调温器还要实现基本的机器学习的功能，以便它能够根据人是否在家而自动控温，这部分代码需要跑在云端上。&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="http://a.36krcnd.com/photo/2014/435c0fea0d3c8633ea3ec25746eadf10.png!heading"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;之所以称这部分软件为“固”件，是因为相对其他软件而言，这部分代码除了研发团队外，在用户使用过程中基本上不会再接触到。如果我们让设备能够联网，就能通过远程让固件升级，方便迭代。如果你使用了Spark Core，就可以通过他们的在线IDE来更新代码。&lt;/p&gt;    &lt;p&gt;调温器的控制部分是通过一个在线云端App来实现的。所以，我们可以改用迭代更快的Ruby on Rails来搞定程序，而不是麻烦的嵌入式C。Spark Cloud通过自带的REST API来连接设备，这意味着你可以将其嵌入到任何可以产生HTTP request的程序中，基本上所有语言都可以做到这一点。用户界面是一个web app，你可以通过上面的javascript来选定你想要的温度。同时，在页面下方会生成一个历史温度的曲线图。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;3.    连接&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;如果你分别搞定了硬件和软件，最简单的把它们连接起来的方法就是通过一个Wi-Fi模块，这样就可以让你的产品成为一个本地Wi-Fi的终端。Spark Core自带Wi-Fi模块，并且因为其内置的微处理器，连接变得非常容易。Spark Core能自动通过加密通道连接Spark Cloud，你无须再额外构建代码来操作Wi-Fi模块，或者是适配通讯协议。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;4.    组装&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="http://a.36krcnd.com/photo/2014/71fbe734d84dc76fb464d5e314312e21.png!heading"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;现在，调温器的所有部分都已将完成了，下一步就是把这些零件都塞进我们的木头外壳中，再把它固定在墙上。想必大家在这部分都不会花太多时间。装配好之后，我们的Nest就算正式完成啦！&lt;/p&gt;    &lt;p&gt;      &lt;img alt="" src="http://a.36krcnd.com/photo/2014/36f29dbb1f9acc259c37d2a69929664c.png!heading"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;p&gt;整个制作过程花费了70美金（其中，Spark Core 39美金）；木材和亚克力板很容易找到。时间上，3.5个工程师从第一天上午十点搞到第二天凌晨3点半（有一个人提前上床睡觉去了）。提前做的工作只是去订购一些电子元件。&lt;/p&gt;    &lt;p&gt;另外， Spark团队强调，这篇文章的目的并不是说任何人都可以在一天时间内创立一家价值32亿美金的公司……但是，现在这事已经变得没那么难了。&lt;/p&gt;    &lt;p&gt;“所有的巨头都来自于一开始几百块钱的产品原型。”所以，你还在等什么？&lt;/p&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/58985-%E6%94%BB%E7%95%A5-%E4%B8%80%E5%A4%A9-%E6%97%B6%E9%97%B4</guid>
      <pubDate>Tue, 20 Nov 2018 19:28:18 CST</pubDate>
    </item>
    <item>
      <title>用Python进行时间序列预测的7种方法</title>
      <link>https://itindex.net/detail/58931-python-%E6%97%B6%E9%97%B4%E5%BA%8F%E5%88%97-%E9%A2%84%E6%B5%8B</link>
      <description>&lt;p&gt;时间序列预测在日常分析中常会用到，前段时间在处理预算相关的内容，涉到一些指标预测，学习到了这篇文章，整理出来分享给大家。&lt;/p&gt;
 &lt;h2&gt;数据准备&lt;/h2&gt;
 &lt;p&gt;数据集（JetRail高铁的乘客数量）下载，链接: https://pan.baidu.com/s/15w5_5_o8IK6ZT3VlNSRa7Q 提取码: 9be3&lt;/p&gt;
 &lt;p&gt;假设要解决一个时序问题：根据过往两年的数据（2012 年 8 月至 2014 年 8月），需要用这些数据预测接下来 7 个月的乘客数量。&lt;/p&gt; &lt;pre&gt;import pandas as pd 
import numpy as np 
import matplotlib.pyplot as plt

df = pd.read_csv(&amp;apos;train.csv&amp;apos;)
df.head()&lt;/pre&gt; &lt;p&gt;  &lt;img alt="" height="163" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/data-demo.png" width="195"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;依照上面的代码，我们获得了 2012-2014 年两年每个小时的乘客数量。为了解释每种方法的不同之处，以每天为单位构造和聚合了一个数据集。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;从 2012 年 8 月- 2013 年 12 月的数据中构造一个数据集。&lt;/li&gt;
  &lt;li&gt;创建 train and test 文件用于建模。前 14 个月（ 2012 年 8 月- 2013 年 10 月）用作训练数据，后两个月（2013 年 11 月 – 2013 年 12 月）用作测试数据。&lt;/li&gt;
  &lt;li&gt;以每天为单位聚合数据集。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;&lt;/p&gt; &lt;pre&gt;import pandas as pd
import matplotlib.pyplot as plt

# Subsetting the dataset
# Index 11856 marks the end of year 2013
df = pd.read_csv(&amp;apos;train.csv&amp;apos;, nrows=11856)

# Creating train and test set
# Index 10392 marks the end of October 2013
train = df[0:10392]
test = df[10392:]

# Aggregating the dataset at daily level
df[&amp;apos;Timestamp&amp;apos;] = pd.to_datetime(df[&amp;apos;Datetime&amp;apos;], format=&amp;apos;%d-%m-%Y %H:%M&amp;apos;)
df.index = df[&amp;apos;Timestamp&amp;apos;]
df = df.resample(&amp;apos;D&amp;apos;).mean()

train[&amp;apos;Timestamp&amp;apos;] = pd.to_datetime(train[&amp;apos;Datetime&amp;apos;], format=&amp;apos;%d-%m-%Y %H:%M&amp;apos;)
train.index = train[&amp;apos;Timestamp&amp;apos;]
train = train.resample(&amp;apos;D&amp;apos;).mean()

test[&amp;apos;Timestamp&amp;apos;] = pd.to_datetime(test[&amp;apos;Datetime&amp;apos;], format=&amp;apos;%d-%m-%Y %H:%M&amp;apos;)
test.index = test[&amp;apos;Timestamp&amp;apos;]
test = test.resample(&amp;apos;D&amp;apos;).mean()

#Plotting data
train.Count.plot(figsize=(15,8), title= &amp;apos;Daily Ridership&amp;apos;, fontsize=14)
test.Count.plot(figsize=(15,8), title= &amp;apos;Daily Ridership&amp;apos;, fontsize=14)
plt.show()&lt;/pre&gt; &lt;p&gt;我们将数据可视化（训练数据和测试数据一起），从而得知在一段时间内数据是如何变化的。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="320" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/data-train-and-test-1024x546.png" width="600"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;方法1：朴素法&lt;/h2&gt;
 &lt;p&gt;假设 y 轴表示物品的价格，x 轴表示时间（天）。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="217" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/native-1.png" width="337"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;如果数据集在一段时间内都很稳定，我们想预测第二天的价格，可以取前面一天的价格，预测第二天的值。这种假设第一个预测点和上一个观察点相等的预测方法就叫朴素法。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="38" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/native-2.png" width="198"&gt;&lt;/img&gt;&lt;/p&gt; &lt;pre&gt;dd = np.asarray(train[&amp;apos;Count&amp;apos;])
y_hat = test.copy()
y_hat[&amp;apos;naive&amp;apos;] = dd[len(dd) - 1]
plt.figure(figsize=(12, 8))
plt.plot(train.index, train[&amp;apos;Count&amp;apos;], label=&amp;apos;Train&amp;apos;)
plt.plot(test.index, test[&amp;apos;Count&amp;apos;], label=&amp;apos;Test&amp;apos;)
plt.plot(y_hat.index, y_hat[&amp;apos;naive&amp;apos;], label=&amp;apos;Naive Forecast&amp;apos;)
plt.legend(loc=&amp;apos;best&amp;apos;)
plt.title(&amp;quot;Naive Forecast&amp;quot;)
plt.show()&lt;/pre&gt; &lt;p&gt;  &lt;img alt="" height="400" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/native-3.png" width="600"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;朴素法并不适合变化很大的数据集，最适合稳定性很高的数据集。我们计算下均方根误差，检查模型在测试数据集上的准确率：&lt;/p&gt; &lt;pre&gt;from sklearn.metrics import mean_squared_error
from math import sqrt

rms = sqrt(mean_squared_error(test[&amp;apos;Count&amp;apos;], y_hat[&amp;apos;naive&amp;apos;]))
print(rms)&lt;/pre&gt; &lt;p&gt;最终均方根误差RMS为：43.91640614391676&lt;/p&gt;
 &lt;h2&gt;方法2：简单平均法&lt;/h2&gt;
 &lt;p&gt;我们假设y轴表示某个物品的价格，x轴表示时间（天）。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="198" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/avg-1.png" width="305"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;物品价格会随机上涨和下跌，平均价格会保持一致。我们经常会遇到一些数据集，虽然在一定时期内出现小幅变动，但每个时间段的平均值确实保持不变。这种情况下，我们可以预测出第二天的价格大致和过去天数的价格平均值一致。这种将预期值等同于之前所有观测点的平均值的预测方法就叫简单平均法。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="63" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/avg-2.png" width="230"&gt;&lt;/img&gt;&lt;/p&gt; &lt;pre&gt;y_hat_avg = test.copy()
y_hat_avg[&amp;apos;avg_forecast&amp;apos;] = train[&amp;apos;Count&amp;apos;].mean()
plt.figure(figsize=(12,8))
plt.plot(train[&amp;apos;Count&amp;apos;], label=&amp;apos;Train&amp;apos;)
plt.plot(test[&amp;apos;Count&amp;apos;], label=&amp;apos;Test&amp;apos;)
plt.plot(y_hat_avg[&amp;apos;avg_forecast&amp;apos;], label=&amp;apos;Average Forecast&amp;apos;)
plt.legend(loc=&amp;apos;best&amp;apos;)
plt.show()&lt;/pre&gt; &lt;p&gt;  &lt;img alt="" height="400" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/avg-3.png" width="600"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;们用之前全部已知的值计算出它们的平均值，将它作为要预测的下一个值。当然这不会很准确，但这种预测方法在某些情况下效果是最好的。&lt;/p&gt;
 &lt;p&gt;该方法的均方根差为：109.88526527082863&lt;/p&gt; &lt;pre&gt;from sklearn.metrics import mean_squared_error
from math import sqrt

rms = sqrt(mean_squared_error(test[&amp;apos;Count&amp;apos;], y_hat_avg[&amp;apos;avg_forecast&amp;apos;]))
print(rms)&lt;/pre&gt; &lt;p&gt;这种模型并没有改善准确率。因此我们可以从中推断出当每个时间段的平均值保持不变时，这种方法的效果才能达到最好。虽然朴素法的准确率高于简单平均法，但这并不意味着朴素法在所有的数据集上都比简单平均法好。&lt;/p&gt;
 &lt;h2&gt;方法3：移动平均法&lt;/h2&gt;
 &lt;p&gt;假设y轴表示某个物品的价格，x轴表示时间（天）。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="234" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/mov-1.png" width="339"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;物品价格在一段时间内大幅上涨，但后来又趋于平稳。我们也经常会遇到这种数据集，比如价格或销售额某段时间大幅上升或下降。如果我们这时用之前的简单平均法，就得使用所有先前数据的平均值，但在这里使用之前的所有数据是说不通的，因为用开始阶段的价格值会大幅影响接下来日期的预测值。因此，我们只取最近几个时期的价格平均值。很明显这里的逻辑是只有最近的值最要紧。这种用某些窗口期计算平均值的预测方法就叫移动平均法。&lt;/p&gt;
 &lt;p&gt;计算移动平均值涉及到一个有时被称为“滑动窗口”的大小值p。使用简单的移动平均模型，我们可以根据之前数值的固定有限数p的平均值预测某个时序中的下一个值。这样，对于所有的 i &amp;gt; p：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="55" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/mov-2.png" width="304"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;移动平均法实际上很有效，特别是当你为时序选择了正确的p值时。（以下程序选择了60天作为窗口大小）&lt;/p&gt; &lt;pre&gt;y_hat_avg = test.copy()
y_hat_avg[&amp;apos;moving_avg_forecast&amp;apos;] = train[&amp;apos;Count&amp;apos;].rolling(60).mean().iloc[-1]
plt.figure(figsize=(16,8))
plt.plot(train[&amp;apos;Count&amp;apos;], label=&amp;apos;Train&amp;apos;)
plt.plot(test[&amp;apos;Count&amp;apos;], label=&amp;apos;Test&amp;apos;)
plt.plot(y_hat_avg[&amp;apos;moving_avg_forecast&amp;apos;], label=&amp;apos;Moving Average Forecast&amp;apos;)
plt.legend(loc=&amp;apos;best&amp;apos;)
plt.show()&lt;/pre&gt; &lt;p&gt;  &lt;img alt="" height="300" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/mov-3.png" width="600"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;此方法计算出来的均方根差为：46.72840725106963&lt;/p&gt; &lt;pre&gt;from sklearn.metrics import mean_squared_error
from math import sqrt

rms = sqrt(mean_squared_error(test[&amp;apos;Count&amp;apos;], y_hat_avg[&amp;apos;moving_avg_forecast&amp;apos;]))
print(rms)&lt;/pre&gt; &lt;p&gt;我们可以看到，对于这个数据集，朴素法比简单平均法和移动平均法的表现要好。此外，我们还可以试试简单指数平滑法，它比移动平均法的一个进步之处就是相当于对移动平均法进行了加权。在上文移动平均法可以看到，我们对“p”中的观察值赋予了同样的权重。但是我们可能遇到一些情况，比如“p”中每个观察值会以不同的方式影响预测结果。将过去观察值赋予不同权重的方法就叫做加权移动平均法。加权移动平均法其实还是一种移动平均法，只是“滑动窗口期”内的值被赋予不同的权重，通常来讲，最近时间点的值发挥的作用更大了。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="53" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/mov-4.png" width="572"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这种方法并非选择一个窗口期的值，而是需要一列权重值（相加后为1）。例如，如果我们选择[0.40, 0.25, 0.20, 0.15]作为权值，我们会为最近的4个时间点分别赋给40%，25%，20%和15%的权重。&lt;/p&gt;
 &lt;h2&gt;方法4：简单指数平滑法&lt;/h2&gt;
 &lt;p&gt;我们注意到简单平均法和加权移动平均法在选取时间点的思路上存在较大的差异。我们就需要在这两种方法之间取一个折中的方法，在将所有数据考虑在内的同时也能给数据赋予不同非权重。例如，相比更早时期内的观测值，它会给近期的观测值赋予更大的权重。按照这种原则工作的方法就叫做简单指数平滑法。它通过加权平均值计算出预测值，其中权重随着观测值从早期到晚期的变化呈指数级下降，最小的权重和最早的观测值相关：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="37" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/ses-1.png" width="562"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;其中0≤α≤1是平滑参数。对时间点T+1的单步预测值是时序  &lt;img alt="y_1,...,y_T" height="14" src="https://www.biaodianfu.com/wp-content/ql-cache/quicklatex.com-cd11200cffac96de3a5805b1fc47404c_l3.png" title="Rendered by QuickLaTeX.com" width="85"&gt;&lt;/img&gt;的所有观测值的加权平均数。权重下降的速率由参数α控制，预测值  &lt;img alt="\hat{y}_x" height="20" src="https://www.biaodianfu.com/wp-content/ql-cache/quicklatex.com-f6e9370f32b862a413c954f2191585c2_l3.png" title="Rendered by QuickLaTeX.com" width="21"&gt;&lt;/img&gt;是  &lt;img alt="\alpha \cdot y_t" height="14" src="https://www.biaodianfu.com/wp-content/ql-cache/quicklatex.com-b3656ecb399ff2185c372ec14f3e6939_l3.png" title="Rendered by QuickLaTeX.com" width="49"&gt;&lt;/img&gt;与  &lt;img alt="(1-\alpha) \cdot \hat{y}_x" height="23" src="https://www.biaodianfu.com/wp-content/ql-cache/quicklatex.com-8ed09bc4ffd78a6346f6638e88f90e4a_l3.png" title="Rendered by QuickLaTeX.com" width="109"&gt;&lt;/img&gt;的和。&lt;/p&gt;
 &lt;p&gt;因此，它可以写为：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="49" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/SES-2.png" width="350"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;所以本质上，我们是用两个权重α和1−α得到一个加权移动平均值。我们可以看到  &lt;img alt="\hat{y}_{x-1}" height="20" src="https://www.biaodianfu.com/wp-content/ql-cache/quicklatex.com-f2afb4fbcac212e8e6a92a539f81f4a9_l3.png" title="Rendered by QuickLaTeX.com" width="43"&gt;&lt;/img&gt;和1−α相乘，让表达式呈递进形式，这也是该方法被称为“指数”的原因。时间 t+1 处的预测值为最近观测值  &lt;img alt="y_t" height="14" src="https://www.biaodianfu.com/wp-content/ql-cache/quicklatex.com-e2e0bacf416d88337160a77476d5152a_l3.png" title="Rendered by QuickLaTeX.com" width="17"&gt;&lt;/img&gt;和最近预测值   &lt;img alt="\hat{y}_{t|t-1}" height="25" src="https://www.biaodianfu.com/wp-content/ql-cache/quicklatex.com-0f7f14307be3ccd18f00e2e894a01d1f_l3.png" title="Rendered by QuickLaTeX.com" width="50"&gt;&lt;/img&gt;之间的加权平均值。&lt;/p&gt; &lt;pre&gt;from statsmodels.tsa.api import SimpleExpSmoothing

y_hat_avg = test.copy()
fit = SimpleExpSmoothing(np.asarray(train[&amp;apos;Count&amp;apos;])).fit(smoothing_level=0.6, optimized=False)
y_hat_avg[&amp;apos;SES&amp;apos;] = fit.forecast(len(test))
plt.figure(figsize=(16, 8))
plt.plot(train[&amp;apos;Count&amp;apos;], label=&amp;apos;Train&amp;apos;)
plt.plot(test[&amp;apos;Count&amp;apos;], label=&amp;apos;Test&amp;apos;)
plt.plot(y_hat_avg[&amp;apos;SES&amp;apos;], label=&amp;apos;SES&amp;apos;)
plt.legend(loc=&amp;apos;best&amp;apos;)
plt.show()&lt;/pre&gt; &lt;p&gt;  &lt;img alt="" height="300" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/SES-3.png" width="600"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;上述方法计算出来的均方根差为：43.357625225228155&lt;/p&gt; &lt;pre&gt;from sklearn.metrics import mean_squared_error
from math import sqrt

rms = sqrt(mean_squared_error(test[&amp;apos;Count&amp;apos;], y_hat_avg[&amp;apos;SES&amp;apos;]))
print(rms)&lt;/pre&gt; &lt;p&gt;模型中使用的α值为0.6，我们可以用测试集继续调整参数以生成一个更好的模型。&lt;/p&gt;
 &lt;h2&gt;方法5：霍尔特(Holt)线性趋势法&lt;/h2&gt;
 &lt;p&gt;假设y轴表示某个物品的价格，x轴表示时间（天）。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="161" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/holt-1.png" width="302"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;如果物品的价格是不断上涨的（见上图），我们上面的方法并没有考虑这种趋势，即我们在一段时间内观察到的价格的总体模式。在上图例子中，我们可以看到物品的价格呈上涨趋势。虽然上面这些方法都可以应用于这种趋势，但我们仍需要一种方法可以在无需假设的情况下，准确预测出价格趋势。这种考虑到数据集变化趋势的方法就叫做霍尔特线性趋势法。&lt;/p&gt;
 &lt;p&gt;每个时序数据集可以分解为相应的几个部分：趋势（Trend），季节性(Seasonal)和残差(Residual)。任何呈现某种趋势的数据集都可以用霍尔特线性趋势法用于预测。&lt;/p&gt; &lt;pre&gt;import statsmodels.api as sm

sm.tsa.seasonal_decompose(train[&amp;apos;Count&amp;apos;]).plot()
result = sm.tsa.stattools.adfuller(train[&amp;apos;Count&amp;apos;])
plt.show()&lt;/pre&gt; &lt;p&gt;  &lt;img alt="" height="480" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/holt-2.png" width="640"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;我们从图中可以看出，该数据集呈上升趋势。因此我们可以用霍尔特线性趋势法预测未来价格。该算法包含三个方程：一个水平方程，一个趋势方程，一个方程将二者相加以得到预测值  &lt;img alt="\hat{y}" height="20" src="https://www.biaodianfu.com/wp-content/ql-cache/quicklatex.com-795f6d98bce74c0311e71e2cac1b12b9_l3.png" title="Rendered by QuickLaTeX.com" width="12"&gt;&lt;/img&gt;：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="138" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/holt-3.png" width="581"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;我们在上面算法中预测的值称为水平（level）。正如简单指数平滑一样，这里的水平方程显示它是观测值和样本内单步预测值的加权平均数，趋势方程显示它是根据 ℓ(t)−ℓ(t−1) 和之前的预测趋势 b(t−1) 在时间t处的预测趋势的加权平均值。&lt;/p&gt;
 &lt;p&gt;我们将这两个方程相加，得出一个预测函数。我们也可以将两者相乘而不是相加得到一个乘法预测方程。当趋势呈线性增加和下降时，我们用相加得到的方程；当趋势呈指数级增加或下降时，我们用相乘得到的方程。实践操作显示，用相乘得到的方程，预测结果会更稳定，但用相加得到的方程，更容易理解。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="322" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/holt-4.png" width="754"&gt;&lt;/img&gt;&lt;/p&gt; &lt;pre&gt;from statsmodels.tsa.api import Holt

y_hat_avg = test.copy()

fit = Holt(np.asarray(train[&amp;apos;Count&amp;apos;])).fit(smoothing_level=0.3, smoothing_slope=0.1)
y_hat_avg[&amp;apos;Holt_linear&amp;apos;] = fit.forecast(len(test))

plt.figure(figsize=(16, 8))
plt.plot(train[&amp;apos;Count&amp;apos;], label=&amp;apos;Train&amp;apos;)
plt.plot(test[&amp;apos;Count&amp;apos;], label=&amp;apos;Test&amp;apos;)
plt.plot(y_hat_avg[&amp;apos;Holt_linear&amp;apos;], label=&amp;apos;Holt_linear&amp;apos;)
plt.legend(loc=&amp;apos;best&amp;apos;)
plt.show()&lt;/pre&gt; &lt;p&gt;  &lt;img alt="" height="300" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/holt-5.png" width="600"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;使用该方法的均方根误差为：43.056259611507286&lt;/p&gt; &lt;pre&gt;from sklearn.metrics import mean_squared_error
from math import sqrt

rms = sqrt(mean_squared_error(test[&amp;apos;Count&amp;apos;], y_hat_avg[&amp;apos;Holt_linear&amp;apos;]))
print(rms)&lt;/pre&gt; &lt;p&gt;这种方法能够准确地显示出趋势，因此比前面的几种模型效果更好。如果调整一下参数，结果会更好。&lt;/p&gt;
 &lt;h2&gt;方法6：Holt-Winters季节性预测模型&lt;/h2&gt;
 &lt;p&gt;在应用这种算法前，我们先介绍一个新术语。假如有家酒店坐落在半山腰上，夏季的时候生意很好，顾客很多，但每年其余时间顾客很少。因此，每年夏季的收入会远高于其它季节，而且每年都是这样，那么这种重复现象叫做“季节性”（Seasonality）。如果数据集在一定时间段内的固定区间内呈现相似的模式，那么该数据集就具有季节性。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="158" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/Holt-Winters-1.jpg" width="446"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;我们之前讨论的5种模型在预测时并没有考虑到数据集的季节性，因此我们需要一种能考虑这种因素的方法。应用到这种情况下的算法就叫做Holt-Winters季节性预测模型，它是一种三次指数平滑预测，其背后的理念就是除了水平和趋势外，还将指数平滑应用到季节分量上。&lt;/p&gt;
 &lt;p&gt;Holt-Winters季节性预测模型由预测函数和三次平滑函数——一个是水平函数ℓt，一个是趋势函数bt，一个是季节分量 st，以及平滑参数α,β和γ。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="156" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/Holt-Winter-2.png" width="558"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;其中 s 为季节循环的长度，0≤α≤ 1, 0 ≤β≤ 1 ， 0≤γ≤ 1。水平函数为季节性调整的观测值和时间点t处非季节预测之间的加权平均值。趋势函数和霍尔特线性方法中的含义相同。季节函数为当前季节指数和去年同一季节的季节性指数之间的加权平均值。在本算法，我们同样可以用相加和相乘的方法。当季节性变化大致相同时，优先选择相加方法，而当季节变化的幅度与各时间段的水平成正比时，优先选择相乘的方法。&lt;/p&gt; &lt;pre&gt;from statsmodels.tsa.api import ExponentialSmoothing

y_hat_avg = test.copy()
fit1 = ExponentialSmoothing(np.asarray(train[&amp;apos;Count&amp;apos;]), seasonal_periods=7, trend=&amp;apos;add&amp;apos;, seasonal=&amp;apos;add&amp;apos;, ).fit()
y_hat_avg[&amp;apos;Holt_Winter&amp;apos;] = fit1.forecast(len(test))
plt.figure(figsize=(16, 8))
plt.plot(train[&amp;apos;Count&amp;apos;], label=&amp;apos;Train&amp;apos;)
plt.plot(test[&amp;apos;Count&amp;apos;], label=&amp;apos;Test&amp;apos;)
plt.plot(y_hat_avg[&amp;apos;Holt_Winter&amp;apos;], label=&amp;apos;Holt_Winter&amp;apos;)
plt.legend(loc=&amp;apos;best&amp;apos;)
plt.show()&lt;/pre&gt; &lt;p&gt;  &lt;img alt="" height="300" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/Holt-Winters-3.png" width="600"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;使用该方法的均方根误差为：23.961492566159794&lt;/p&gt; &lt;pre&gt;from sklearn.metrics import mean_squared_error
from math import sqrt

rms = sqrt(mean_squared_error(test[&amp;apos;Count&amp;apos;], y_hat_avg[&amp;apos;Holt_Winter&amp;apos;]))
print(rms)&lt;/pre&gt; &lt;p&gt;我们可以看到趋势和季节性的预测准确度都很高。我们选择了 seasonal_period = 7作为每周重复的数据。也可以调整其它其它参数，我在搭建这个模型的时候用的是默认参数。你可以试着调整参数来优化模型。&lt;/p&gt;
 &lt;h2&gt;方法7：自回归移动平均模型（ARIMA）&lt;/h2&gt;
 &lt;p&gt;另一个场景的时序模型是自回归移动平均模型（ARIMA）。指数平滑模型都是基于数据中的趋势和季节性的描述，而自回归移动平均模型的目标是描述数据中彼此之间的关系。ARIMA的一个优化版就是季节性ARIMA。它像Holt-Winters季节性预测模型一样，也把数据集的季节性考虑在内。&lt;/p&gt; &lt;pre&gt;import statsmodels.api as sm

y_hat_avg = test.copy()
fit1 = sm.tsa.statespace.SARIMAX(train.Count, order=(2, 1, 4), seasonal_order=(0, 1, 1, 7)).fit()
y_hat_avg[&amp;apos;SARIMA&amp;apos;] = fit1.predict(start=&amp;quot;2013-11-1&amp;quot;, end=&amp;quot;2013-12-31&amp;quot;, dynamic=True)
plt.figure(figsize=(16, 8))
plt.plot(train[&amp;apos;Count&amp;apos;], label=&amp;apos;Train&amp;apos;)
plt.plot(test[&amp;apos;Count&amp;apos;], label=&amp;apos;Test&amp;apos;)
plt.plot(y_hat_avg[&amp;apos;SARIMA&amp;apos;], label=&amp;apos;SARIMA&amp;apos;)
plt.legend(loc=&amp;apos;best&amp;apos;)
plt.show()&lt;/pre&gt; &lt;p&gt;  &lt;img alt="" height="300" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/ARIMA-1.png" width="600"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;使用该方法的均方根误差为：26.052705330843708&lt;/p&gt; &lt;pre&gt;from sklearn.metrics import mean_squared_error
from math import sqrt

rms = sqrt(mean_squared_error(test[&amp;apos;Count&amp;apos;], y_hat_avg[&amp;apos;SARIMA&amp;apos;]))
print(rms)&lt;/pre&gt; &lt;p&gt;我们可以看到使用季节性 ARIMA 的效果和Holt-Winters差不多。我们根据 ACF（自相关函数）和 PACF（偏自相关） 图选择参数。如果你为 ARIMA 模型选择参数时遇到了困难，可以用 R 语言中的 auto.arima。&lt;/p&gt;
 &lt;p&gt;最后，我们将这几种模型的准确度比较一下：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="306" src="https://www.biaodianfu.com/wp-content/uploads/2018/10/model-rank.png" width="338"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;后话&lt;/h2&gt;
 &lt;p&gt;希望本文对你有所帮助，在解决时许问题的时候能从容以对。我建议你在解决问题时，可以依次试试这几种模型，看看哪个效果最好。我们从上文也知道，数据集不同，每种模型的效果都有可能优于其它模型。因此，如果一个模型在某个数据集上效果很好，并不代表它在所有数据集上都比其它模型好。&lt;/p&gt;
 &lt;p&gt;原文地址：  &lt;a href="https://www.analyticsvidhya.com/blog/2018/02/time-series-forecasting-methods/"&gt;https://www.analyticsvidhya.com/blog/2018/02/time-series-forecasting-methods/&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;后续TODO :&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;深入学习statsmodels&lt;/li&gt;
  &lt;li&gt;深入学习Holt-Winters季节性预测模型&lt;/li&gt;
  &lt;li&gt;深入学习自回归移动平均模型（ARIMA）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;The post   &lt;a href="https://www.biaodianfu.com/python-time-series-forecasting-methods.html" rel="nofollow"&gt;用Python进行时间序列预测的7种方法&lt;/a&gt; appeared first on   &lt;a href="https://www.biaodianfu.com" rel="nofollow"&gt;标点符&lt;/a&gt;.&lt;/p&gt;
 &lt;div&gt;
  &lt;p&gt;Related posts:&lt;/p&gt;  &lt;ol&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/using-mahalanobis-distance-to-find-outliers.html" rel="bookmark" title="&amp;#20351;&amp;#29992;&amp;#39532;&amp;#27663;&amp;#36317;&amp;#31163;&amp;#21457;&amp;#29616;&amp;#24322;&amp;#24120;&amp;#28857;"&gt;使用马氏距离发现异常点 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/k-means-choose-k.html" rel="bookmark" title="K-Means&amp;#31639;&amp;#27861;&amp;#20043;K&amp;#20540;&amp;#30340;&amp;#36873;&amp;#25321;"&gt;K-Means算法之K值的选择 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/clustering-on-a-one-dimensional-array.html" rel="bookmark" title="&amp;#19968;&amp;#32500;&amp;#25968;&amp;#32452;&amp;#30340;&amp;#32858;&amp;#31867;"&gt;一维数组的聚类 &lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;  &lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>数据科学 Python 预测</category>
      <guid isPermaLink="true">https://itindex.net/detail/58931-python-%E6%97%B6%E9%97%B4%E5%BA%8F%E5%88%97-%E9%A2%84%E6%B5%8B</guid>
      <pubDate>Fri, 02 Nov 2018 09:04:49 CST</pubDate>
    </item>
    <item>
      <title>以秒为单位生成唯一的时间序列号</title>
      <link>https://itindex.net/detail/58695-%E5%8D%95%E4%BD%8D-%E5%94%AF%E4%B8%80-%E6%97%B6%E9%97%B4%E5%BA%8F%E5%88%97</link>
      <description>&lt;div&gt;
  &lt;pre&gt;/**
 * 功能： 以秒为单位生成唯一的序列号 &amp;lt;br/&amp;gt;
 * 生成格式：YYMMddHHmmssXXXXXXX &amp;lt;br/&amp;gt;
 * XXXXXXX：代表序列号，从1开始
 * 例子: 1808312321280000001 或者 1808312321280001234 &amp;lt;br/&amp;gt;
 * &amp;lt;p&amp;gt;局限性： 每秒生成最大范围 (1000万-1) 个数&amp;lt;/p&amp;gt;
 * Created by Administrator on 2018/8/31.
 */
public final class GenerateDateTimeUniqueID {

    private static final DateFormat DF = new SimpleDateFormat(&amp;quot;yyMMddHHmmss&amp;quot;);
    private static volatile long LAST_TIME = -1;
    private static final AtomicInteger COUNT = new AtomicInteger();
    private static final Object LOCK = new Object();

    //测试是否有生成重复的ID
    public static final ConcurrentMap&amp;lt;String, Boolean&amp;gt; MAP = new ConcurrentHashMap&amp;lt;String, Boolean&amp;gt;();

    //1 000 000
    private static final byte LEVEL = 7; //限定一秒钟最多产生1000万-1 个数
    private static final String ZERO_0 = &amp;quot;&amp;quot;;
    private static final String ZERO_1 = &amp;quot;0&amp;quot;;
    private static final String ZERO_2 = &amp;quot;00&amp;quot;;
    private static final String ZERO_3 = &amp;quot;000&amp;quot;;
    private static final String ZERO_4 = &amp;quot;0000&amp;quot;;
    private static final String ZERO_5 = &amp;quot;00000&amp;quot;;
    private static final String ZERO_6 = &amp;quot;000000&amp;quot;;


    /*******
     *
     * 测试机器系统参数： Win7 64位 i5-4210M 4core 2.6GHz 内存8GB
     *
     * ********/


    /**
     * 测试10个线程并发产生，每秒可以产生310万左右个序列号
     * *
     */
    public static String generateDateTimeUniqueIdStr() throws Exception {
        Date date = new Date();
        String dateStr = DF.format(date);
        long curTime = Long.parseLong(dateStr);
        int curCount = 0;

        synchronized (GenerateDateTimeUniqueID.class) {
            if (curTime &amp;lt; LAST_TIME) {
                curTime = LAST_TIME;
            } else if (curTime &amp;gt; LAST_TIME) {
                LAST_TIME = curTime;
                System.out.println(Thread.currentThread().getName() + &amp;quot;-&amp;quot; + COUNT.get());
                COUNT.set(0);
            }
            curCount = COUNT.incrementAndGet();
        }

        return curTime + format(curCount);
    }

    /**
     * 测试10个线程并发产生，每秒可以产生290万左右个序列号
     * *
     */
    public static long generateDateTimeUniqueId() {
        Date date = new Date();
        String dateStr = DF.format(date);
        long curTime = Long.parseLong(dateStr);
        int curCount = 0;

        synchronized (GenerateDateTimeUniqueID.class) {
            if (curTime &amp;lt; LAST_TIME) {
                curTime = LAST_TIME;
            } else if (curTime &amp;gt; LAST_TIME) {
                LAST_TIME = curTime;
                System.out.println(Thread.currentThread().getName() + &amp;quot;-&amp;quot; + COUNT.get());
                COUNT.set(0);
            }
            curCount = COUNT.incrementAndGet();
        }

        return Long.parseLong(curTime + format(curCount));
    }


    private static String format(int value) {
        int temp = value;
        int count = 0;
        while (temp &amp;gt; 0) {
            temp = temp / 10;
            count++;
        }
        switch (LEVEL - count) {
            case 0:
                return ZERO_0 + value;
            case 1:
                return ZERO_1 + value;
            case 2:
                return ZERO_2 + value;
            case 3:
                return ZERO_3 + value;
            case 4:
                return ZERO_4 + value;
            case 5:
                return ZERO_5 + value;
            case 6:
                return ZERO_6 + value;
            default:
                return &amp;quot;9999999&amp;quot;;
//                throw new IllegalStateException(); //再处理
        }
    }

    private GenerateDateTimeUniqueID() {
    }

    public static void main(String[] args) {

        long cur = System.currentTimeMillis();
        for (int i = 1; i &amp;lt; 1000000000; i++) {
            System.out.println(generateDateTimeUniqueId());

        }

//        int num = 6;
//        ExecutorService executorService = Executors.newFixedThreadPool(num);
//        for (int i = 0; i &amp;lt; num; i++) {
//            executorService.submit(new TestThread());
//        }
        System.out.println(&amp;quot;spend &amp;quot; + (System.currentTimeMillis() - cur) + &amp;quot; ms&amp;quot;);
    }

    static class TestThread implements Runnable {

        @Override
        public void run() {
            while (true) {
                String id = generateDateTimeUniqueIdStr();
                Boolean isHas = MAP.putIfAbsent(id, Boolean.TRUE);
                if (isHas != null) {
                    throw new IllegalArgumentException();
                }
            }
        }
    }
}
&lt;/pre&gt;
  &lt;p&gt; &lt;/p&gt;
&lt;/div&gt;
          
           &lt;br /&gt; &lt;br /&gt;
          
             &lt;a href="http://ihenu.iteye.com/blog/2429903#comments"&gt;已有   &lt;strong&gt;0&lt;/strong&gt; 人发表留言，猛击-&amp;gt;&amp;gt;  &lt;strong&gt;这里&lt;/strong&gt;&amp;lt;&amp;lt;-参与讨论&lt;/a&gt;
          
           &lt;br /&gt; &lt;br /&gt; &lt;br /&gt;
ITeye推荐
 &lt;br /&gt;
 &lt;ul&gt;  &lt;li&gt;   &lt;a href="http://www.iteye.com/clicks/433" target="_blank"&gt;—软件人才免语言低担保 赴美带薪读研！— &lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
 &lt;br /&gt; &lt;br /&gt; &lt;br /&gt;
          
        &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/58695-%E5%8D%95%E4%BD%8D-%E5%94%AF%E4%B8%80-%E6%97%B6%E9%97%B4%E5%BA%8F%E5%88%97</guid>
      <pubDate>Sat, 01 Sep 2018 23:10:40 CST</pubDate>
    </item>
    <item>
      <title>研究称新兴世界家长花更多时间辅导子女</title>
      <link>https://itindex.net/detail/58483-%E7%A0%94%E7%A9%B6-%E6%96%B0%E5%85%B4-%E4%B8%96%E7%95%8C</link>
      <description>Varkey Foundation 对 29 个国家逾 2.7 万名家长的研究 &lt;a href="http://www.ftchinese.com/story/001078167" target="_blank"&gt;发现&lt;/a&gt;，在教育观方面，新兴经济体中的父母与西方的父母处于不同的世界。整个新兴世界的父母在辅导孩子学习方面花费的时间都要远远超过发达国家的家长：印度父母在辅导孩子学习方面花费的时间超过所有其他国家接受调查的父母，62% 的人称他们每周辅导孩子学习的时间不少于 7 个小时；越南一半父母为辅导孩子贡献了这么长的时间，而在哥伦比亚，39% 的父母每周为辅导孩子花费的时间不少于 7 个小时。这种情况与英国和法国等欧洲国家的情况截然不同——英法两国仅有 11% 的父母每周辅导孩子学习至少 7 个小时，而芬兰的这一比例仅为 5%。 &lt;p&gt;  &lt;img height="120" src="https://img.solidot.org/0/446/liiLIZF8Uh6yM.jpg"&gt;&lt;/img&gt;&lt;/p&gt; &lt;div&gt;
  &lt;a href="http://feeds.feedburner.com/~ff/solidot?a=lVTwSJGxl1c:NthFHC2osKk:yIl2AUoC8zA"&gt;   &lt;img border="0" src="http://feeds.feedburner.com/~ff/solidot?d=yIl2AUoC8zA"&gt;&lt;/img&gt;&lt;/a&gt;   &lt;a href="http://feeds.feedburner.com/~ff/solidot?a=lVTwSJGxl1c:NthFHC2osKk:7Q72WNTAKBA"&gt;   &lt;img border="0" src="http://feeds.feedburner.com/~ff/solidot?d=7Q72WNTAKBA"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/58483-%E7%A0%94%E7%A9%B6-%E6%96%B0%E5%85%B4-%E4%B8%96%E7%95%8C</guid>
      <pubDate>Tue, 26 Jun 2018 20:59:47 CST</pubDate>
    </item>
    <item>
      <title>异常检测之时间序列的异常检测</title>
      <link>https://itindex.net/detail/58415-%E5%BC%82%E5%B8%B8%E6%A3%80%E6%B5%8B-%E6%97%B6%E9%97%B4%E5%BA%8F%E5%88%97-%E5%BC%82%E5%B8%B8%E6%A3%80%E6%B5%8B</link>
      <description>&lt;div&gt;问题的输入：一个时间序列    &lt;p&gt;问题的输出：是否异常&lt;/p&gt;    &lt;h2&gt;1. 3倍方差&lt;/h2&gt;    &lt;p&gt;其实之前介绍过3倍方差，只是，这里的3倍方差讲的是在时间序列异常检测中的应用。&lt;/p&gt;    &lt;p&gt;一个很直接的异常判定思路是，拿最新3个数据点的平均值（tail_avg方法）和整个序列比较，看是否偏离历史总体平均水平太多，如果偏离太多，就报警。      &lt;br /&gt;&lt;/p&gt;    &lt;h2&gt;2. first hour average&lt;/h2&gt;    &lt;p&gt;算法步骤：&lt;/p&gt;    &lt;p&gt;和上述算法基本一致，只是比较对象不是整个序列，而是开始一个小时（其实这种这种思想可以推广，只要是时间序列刚开始的一段时间即可）的以内的数据，求出这段时间的均值和标准差和尾部数据（新产生的数据）用三本方差的方法比较即可。&lt;/p&gt;    &lt;h2&gt;3. stddev from moving average&lt;/h2&gt;    &lt;p&gt;先求出最后一个点处的指数加权移动平均值，然后再用最新的点和三倍方差方法求异常。      &lt;br /&gt;&lt;/p&gt;    &lt;h2&gt;4. 移动平均&lt;/h2&gt;    &lt;p&gt;给定一个时间序列和窗口长度N，moving average = 当前data point之前N个点（包括当前点）的平均值。不停地移动这个窗口，就得到移动平均曲线。&lt;/p&gt;    &lt;h2&gt;5. 指数移动平均&lt;/h2&gt;    &lt;p&gt;指数移动与移动平均有些不同：&lt;/p&gt;    &lt;p&gt;a. 并没有时间窗口，用的是从时间序列第一个data point到当前data point之间的所有点。&lt;/p&gt;    &lt;p&gt;b. 每个data point的权重不同，离当前时间点越近的点的权重越大，历史时间点的权重随着离当前时间点的距离呈指数衰减,从当前data point i往前的data point，权重依次为α, α(1-α), α(1-α)^2….., α(1-α)^n&lt;/p&gt;    &lt;p&gt;该算法可以检测一个异常较短时间后发生另外一个异常的情况，异常持续一段时间后可能被判定为正常。&lt;/p&gt;    &lt;h2&gt;6. mean subtraction cumulation&lt;/h2&gt;    &lt;p&gt;算法步骤：&lt;/p&gt;    &lt;p&gt;a. 排除掉全序列最后一个点&lt;/p&gt;    &lt;p&gt;b. 计算剩余点的平均值&lt;/p&gt;    &lt;p&gt;c. 所有点减去上一步计算的平均值，得到新的序列&lt;/p&gt;    &lt;p&gt;d. 求剩余序列的标准差&lt;/p&gt;    &lt;p&gt;e. 判断步骤c中得到的新序列的最后一个值是否大于3倍标准差&lt;/p&gt;    &lt;h2&gt;7. 最小二乘法&lt;/h2&gt;    &lt;p&gt;算法步骤：&lt;/p&gt;    &lt;p&gt;a. 用最小二乘法拟合时间序列&lt;/p&gt;    &lt;p&gt;b. 用实际值减去拟合值得到新序列&lt;/p&gt;    &lt;p&gt;c. 判断新序列最后三个值的平均值是否大于新序列的三倍标准差&lt;/p&gt;    &lt;br /&gt;    &lt;h2&gt;   &lt;br /&gt;&lt;/h2&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/58415-%E5%BC%82%E5%B8%B8%E6%A3%80%E6%B5%8B-%E6%97%B6%E9%97%B4%E5%BA%8F%E5%88%97-%E5%BC%82%E5%B8%B8%E6%A3%80%E6%B5%8B</guid>
      <pubDate>Mon, 28 May 2018 18:22:00 CST</pubDate>
    </item>
    <item>
      <title>36氪领读 | 内容推荐算法，抢夺用户时间的利器</title>
      <link>https://itindex.net/detail/58370-%E6%8E%A8%E8%8D%90%E7%AE%97%E6%B3%95-%E7%94%A8%E6%88%B7-%E6%97%B6%E9%97%B4</link>
      <description>&lt;p&gt;36氪专门为读书设立了【36氪领读】栏目，筛选一些值得读的书，并提供一些书摘。希望你手边有一本称心的书，让读书这场运动继续下去。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://pic.36krcnd.com/201805/10101121/fybwuo1fy87badcp.png!heading"&gt;&lt;/img&gt;&lt;/p&gt; &lt;h3&gt;内容简介&lt;/h3&gt; &lt;p&gt;伴随着国民总时间概念的兴起，互联网巨头纷纷布局内容行业，以争夺用户时间。作为内容生产者主力的各类自媒体也如雨后春笋般涌现，在内容创作、内容变现等方面做得风生水起。通过算法实现的推荐技术基于用户历史数据和行为，推测用户意图，推荐合适的商品和内容给终端用户，显著提高了用户的点击率和留存率。随着用户的个性价值越来越被重视，内容推荐分发技术势必会得到更普遍的应用。&lt;/p&gt; &lt;p&gt;内容行业资深从业者、今日头条前资深产品经理闫泽华，在《内容算法》一书中，通过大量生动的案例，图文并茂、深入浅出地分析了当下主流的推荐算法及其利弊，介绍了推荐分发系统相关的知识，同时对自媒体如何实现优质作品最大化传播以及自媒体数据分析、运营与变现等进行了深入解读，有内容、有深度、有态度，无难度。&lt;/p&gt; &lt;h3&gt;作者简介&lt;/h3&gt; &lt;p&gt;  &lt;strong&gt;闫泽华：&lt;/strong&gt;简书签约作者。知乎知识市场产品总监，负责内容付费的产品运营工作。&lt;/p&gt; &lt;p&gt;曾任今日头条资深产品经理、“凯叔讲故事”技术负责人和百度搜索架构工程师。在今日头条工作期间，曾先后负责头条视频的数据流和策略分发，头条号粉丝变现相关业务和微头条的策略分发业务，历经了头条视频和粉丝业务快速增长的全过程。&lt;/p&gt; &lt;h3&gt;书籍摘录&lt;/h3&gt; &lt;p&gt;载体迭代，比快更快。&lt;/p&gt; &lt;p&gt;分发迭代，比快更快。&lt;/p&gt; &lt;p&gt;与其焦虑，不如逐浪。 &lt;/p&gt; &lt;p&gt;但是，在快速的迭代过程中，我相信一定有可以让我们慢下来的东西——那是内容的核。敬畏内容的价值，尊重它给受众带来的价值。&lt;/p&gt; &lt;h2&gt;带着偏见看推荐 &lt;/h2&gt; &lt;p&gt;也许，这一章的名字叫作“竟然被骗了?! 关于算法推荐的 3 个误解，快来看看你有没有？”效果更好。  &lt;/p&gt; &lt;p&gt;推荐其实不是一个新事物，它早已被应用在了如淘宝购物推荐、豆瓣电影推荐、点评美食推荐、携程差旅推荐等不同的场景中，以提升服务质量、优化系统效率。 &lt;/p&gt; &lt;p&gt;当推荐技术应用于内容分发领域后，在已有传统媒体、门户网站仍然占据重要分发位置的情况下，质疑的声浪频传。如果你经历过PC 时代，也许会觉得这一幕似曾相识：当年，传统媒体对门户发出过质疑声，中心化的门户模式对去中心化的微博模式同样质疑过。 &lt;/p&gt; &lt;p&gt;只是岁月轮回，昨日的挑战者成了今日的卫道士，何其唏嘘。在本章里，我所做的并非辩护，因为真实的数据表现和传统新闻应用的推荐化转型都充分证明了内容推荐模式是有一定存在价值和发展空间的。 &lt;/p&gt; &lt;p&gt;我试图从自己的认知角度，对一些常见的误解进行阐述供大家参考。 &lt;/p&gt; &lt;p&gt;站在从业者的角度，我觉得现在的内容推荐产品还远远没有达到理想态，其迭代过程中是存在种种瑕疵和待改进空间的。但发展的进程并不会因为人们的误解而止步。我坚信，科技产品的存在是为了让生活更加美好。 &lt;/p&gt; &lt;p&gt;  &lt;strong&gt;信息茧房&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;当内容分发全面进入推荐分发时代，对这一分发模式的质疑也就越来越多，很多人站出来号召大家要一起戳破“算法的泡泡”。 &lt;/p&gt; &lt;p&gt;一种普遍观点是：机器算法主导的精准分发，在提升阅读体验的同时，也极易导致由信息收窄带来的一叶障目。即在算法分发下，用户将深陷信息茧房当中。 &lt;/p&gt; &lt;p&gt;哈佛大学法学院教授、美国前总统奥巴马的法律顾问凯斯·桑斯在其 2006 年的著作《信息乌托邦——众人如何生产知识》中提出了“信息茧房”这一概念。通过对互联网的考察，桑斯坦指出，在信息传播中，由于公众自身的信息需求并非全方位的，公众只注意自己选择的东西和使自己愉悦的领域，久而久之，会将自身桎梏于像蚕茧一般的“茧房”中。 &lt;/p&gt; &lt;p&gt;这顶“信息茧房”的帽子，随着信息流量分发的迁移，被扣到了不同的内容分发服务头上：在国外，先后被吐槽的是谷歌的个性化搜索结果和脸谱网的信息流服务。在国内，早在 2012 年就有人吐槽微博是“信息茧房”，如今吐槽的对象又变成了机器推荐分发。 &lt;/p&gt; &lt;p&gt;当信息生产的门槛不断降低造成内容量的大繁荣，当信息消费者的选择权越来越大、越来越能够主动选择而非被动接受的时候，令人担忧的“信息茧房”就会被越来越多地提及。 &lt;/p&gt; &lt;p&gt;然而，过滤你的并不是算法泡泡。 &lt;/p&gt; &lt;p&gt;在纸媒时代，当用户从特定的媒体人、特定的媒体刊物处获取信息的时候，其信息获取方式不就是纸媒版的订阅关系分发吗？各家纸媒有自己的题材偏好和内容风格，不也构成了一个“茧房”吗？当面对报亭中琳琅满目的刊物和邮局的订阅表时，用户的主动选择便构成了他的认知世界。 &lt;/p&gt; &lt;p&gt;还记得那本月发行量逾 700 万册的杂志——《知音》吗？其刊载的内容正是今天饱受诟病的典型标题党样例：《风之谷啊我的妹妹，哥哥的未来献给你》（2007 年第 7 期）、《再大的恨放下吧，唤醒前夫赢得亲情一片天》（2007 年第 35 期）。 &lt;/p&gt; &lt;p&gt;在 10 年前，为什么会有那么多人消费这本杂志，在 10 年后的今天，就为什么会有同样规模的人在消费着类似内容。从用户需求的角度看，这些内容符合大众用户偏好、可以满足他们打发时间的需求， &lt;/p&gt; &lt;p&gt;高阅读量就是用户主动选择的结果。 &lt;/p&gt; &lt;p&gt;内容作为一种消费品，每个人都有选择消费途径和消费内容的权利。无论是纸媒还是网媒，只要有足够大的候选集和主动选择权，用户就一定会选择自己更为偏好的信息载体和信息源。从内容匮乏到内容繁荣，从中心化一统到垂直化聚群，用户的选择更贴近自身喜好是不可逆转的趋势。&lt;/p&gt; &lt;p&gt;那些持续表示担心的人，本身就是多源信息的消费者，希望看到多源信息的渴望胜过了他们看到不感兴趣内容的不悦。而被担心的人，本身就不曾有信息多样性的焦虑，乐得在自己的小圈圈里打转。 &lt;/p&gt; &lt;p&gt;这就形成了一边有人大声疾呼，一边有人甘之如饴的局面：担心的人不曾被过滤，被过滤的人不曾担心。 &lt;/p&gt; &lt;p&gt;进一步，对那些担心的人来说：他们跳得出算法分发，也跳不出社交关系分发；跳得出社交关系分发，也跳不出自己的认知选择边界。“茧房”始终存在，真正过滤你的，是你的认知泡泡。 &lt;/p&gt; &lt;p&gt;在这种情况下，将“信息茧房”归罪于分发模式更像是因失语而产生的牢骚。与其吐槽分发模式，不如考虑在新的模式下如何优化；与其大声疾呼内容分发服务商的道德义务，不如将其归结为内容服务商的利益诉求。 &lt;/p&gt; &lt;p&gt;要知道，“信息茧房”并不是内容服务商的诉求，“用户茧房”才是其诉求。为保证用户更好地长期留存，内容服务商就势必需要不断探索用户的内容兴趣偏好、了解用户的观点偏好：你以为只给用户展示他喜欢的类目就够了？那你错过的就是拓展用户兴趣认知的机会。只有给用户展示多元化的内容，才能够让他消费更多内容，停留更长时间。 &lt;/p&gt; &lt;p&gt;你以为只给用户推荐他偏好的观点就够了？那你就可能错过用户间的讨论和互动碰撞。给用户展示多元化甚至有冲突的观点，对评论的刺激作用是显著的。而在多元化的讨论之后，内容丰富的评论区也具有了可以消费的价值。 &lt;/p&gt; &lt;p&gt;如果你的认知还停留在算法分发只会给你推荐感兴趣的内容上，那你的认知泡泡，也该戳一戳了。 &lt;/p&gt; &lt;p&gt;  &lt;strong&gt;推荐会导致Low ？&lt;/strong&gt; &lt;/p&gt; &lt;p&gt;对内容推荐的又一个常见误解是：推荐系统会趋向于低质量和Low（低格调）的内容。做出直观判断很容易，深究这一判断背后的原委却并非易事。那么，让我们来仔细讨论一下。 &lt;/p&gt; &lt;p&gt;  &lt;strong&gt;1.三问内容质量&lt;/strong&gt;  &lt;strong&gt; &lt;/strong&gt;&lt;/p&gt; &lt;p&gt;既然说内容质量低，那总得有个标准吧，这就引入了第一个问题：什么样的内容是质量好的内容？ &lt;/p&gt; &lt;p&gt;当我向朋友提出这个问题时，大家往往会陷入短暂的沉默：是啊，对于食品，我们有诸如保质期、配料成分表、制作工艺等方面的要求；对于衣物，我们有型号、材质、洗涤方式的规范；对于内容，这种非标准化的手工产品，是否也存在一套类似ISO9001 的标准可以衡量呢？ &lt;/p&gt; &lt;p&gt;幸亏身边有纸媒的前辈，让我得以请教纸媒对内容的衡量标准。 &lt;/p&gt; &lt;p&gt;其曾供职的报社，对优质内容的衡量标准如下： &lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;受众角度：读者关注面广，在社会上引起较大反响，为报纸争得明显的效益或荣誉，收获普遍好评的热点、焦点新闻稿。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;策划角度：富有策划、创新意识，极具冲击力和感染力的报道。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;深度角度：分析透彻，有独到见解的深度报道。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;题材角度：重大独家新闻，重大调查性报道。 &lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;“较大反响”“深度”“重大”“普遍好评”，我们不难发现，上述标准多为难以量化的描述性用语。除了第一条站在受众角度——“读者关注面广”“较大反响”，我们尚可以用阅读量、转发量、评论量等数据指标度量外，第二条到第四条都是从创作角度出发，属于主观判断的范畴。对特定垂直行业来说，资深从业者或许有资格能够进行内容深度的判断。对平台来说，通常会涉及多个垂直行业，我们只能以频道编辑建制的方式来解决这个问题。大众说好，专家也说好，“叫好叫座”大概是好内容最理想的终局，一如触乐网的那篇《在三和玩游戏的人们》。 &lt;/p&gt; &lt;p&gt;在达成“好”的共识之后，进一步的问题是：如果一篇内容专家不叫好，这样的内容是否应该被过滤掉呢？ &lt;/p&gt; &lt;p&gt;未必。 &lt;/p&gt; &lt;p&gt;在看过爆米花电影之后，我有翻豆瓣电影看影评的习惯。可看完影评之后我常常会自惭形秽：明明自己觉得还不错的电影，为什么在影评人眼里变得满目疮痍，充满各种各样的问题呢？自己觉得还不错的桥段，在影评人眼中却成了陈词滥调，缺乏创新与深度。 &lt;/p&gt; &lt;p&gt;在猫眼电影工作的朋友的一番解释让我释然：“你是观众视角，影评人是专业视角。专业的判断跟大众的喜好通常会存在认知背景的偏差，在技法上有待改进的内容并不意味着缺乏受众。一如范雨素的爆红，从文字技法上看，她的内容一定是有缺失的，但她对生活的记录触动了许多人的心。” &lt;/p&gt; &lt;p&gt;诚然，如果交由特定群体来判断，或许永远无法产出如“快手”这样的产品。同一段小视频，你看到的是残酷底层物语，他看到的则是生活的延展。 &lt;/p&gt; &lt;p&gt;在今日头条上，同样有这样的账号：农民王小。用直白的影像记录生活，记录老妈做饭，记录姐姐、姐夫给爸妈送米面油……视频的艺术性？连我这个外行都觉得谈不上。但就是这样的账号，已经累积了 45 万名粉丝，每一条视频都有几十万甚至上百万次的播放量，上千条评论。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://pic.36krcnd.com/201805/10102339/idwq9i8isp9o4nik.png!heading"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt; 今日头条“农民王小”账号内容 &lt;/p&gt; &lt;p&gt;这样的内容，我们怎么评价？它也许不能称之为“好”，但也绝对不至于被定位为低质的内容。正如面对鸡蛋与石头的冲突，我们或许首先应该倾向于鸡蛋。如果进行过度专业化的过滤，或许这样的内容将永无出头之日。 &lt;/p&gt; &lt;p&gt;那就回归到最后一个问题：什么样的内容是低质的，应该被过滤？虽然对内容质量的判断存在主观性，但并不意味着我们丧失了对内容质量的约束。借助大量的案例分析，我们能够抽离出一些客观指标，也能达成一些平台审核层的主观一致标准，以较少争议、控制误伤量的方式给出评判“内容质量差、不宜传播”的标准： &lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;真实性上：歪曲事实，虚假信息等。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;阅读体验上：无意义内容，错别字连篇，文不对题，语句不通顺、掺杂广告等。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;价值引导上：不正确的价值观导向，煽动对立情绪，低俗色情等。 &lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;我猜想这也是快手对特定内容做出干预的原因，记录这些内容也许无罪，但这些内容并不适合传播。在明确的低质内容底线之上，平台方或许应该对内容和内容创作者保持足够的宽容。 &lt;/p&gt; &lt;p&gt;  &lt;strong&gt;2.为什么会觉得推荐内容格调低劣&lt;/strong&gt; &lt;/p&gt; &lt;p&gt;既然推荐系统已经将绝对低质的内容过滤掉了，那么，为什么我们还会时不时地觉得推荐内容格调低劣呢？这主要有以下两个原因： &lt;/p&gt; &lt;p&gt;其一，推荐准确性问题。 &lt;/p&gt; &lt;p&gt;一方面，每个人的认知程度是不同的，如果一篇内容是低于我们认知水平的，那我们一定会觉得它质量一般、内容格调偏低劣。就像知乎的Live 产品，不管是受到多少好评的讲座，总有人会给出中评或差评：“格调太低劣了，没啥干货。”另一方面，我们对某些内容存在个人偏见。比如，有些人认为郭德纲的相声是“三俗”的，有些人觉得八卦新闻是不好的，等等。如果推荐不能很好地匹配用户的兴趣偏好或认知程度，就会让用户产生内容格调低劣的感觉。 &lt;/p&gt; &lt;p&gt;在冷启动阶段，由于对用户缺乏认知，推荐并不能很好地刻画用户的兴趣画像。为了最大化地实现用户冷启动阶段的留存目标，推荐必然会从大众化和基础化的热门角度切入。而大众化的内容必然是与部分用户的认知水平存在差异性的，从而让这部分用户认为推荐内容是低质的。 &lt;/p&gt; &lt;p&gt;随着用户的点击动作、阅读表现、关注行为的积累，基于用户聚类和用户偏好作者的信息，推荐不准的情况能够在一定程度上得到缓解。比如，一个人常看科技内容，不点击娱乐内容或很少看完八卦新闻，系统会大体上知道他的调性，不再推荐他不感兴趣、觉得格调低劣的娱乐八卦新闻。 &lt;/p&gt; &lt;p&gt;在推荐过程中，更难解决的是认知水平的问题。系统基于文本分析确定了内容分类，可能会推荐一篇质量平平的科技分析给一个重度的科技用户。在这种情况下，用户还是会觉得内容过水，从而对推荐产生微词。我们通常依赖用户对作者的关注程度来局部改善此类问题。 &lt;/p&gt; &lt;p&gt;其二，用户无意识反馈问题。 &lt;/p&gt; &lt;p&gt;先讲个真实的案例： &lt;/p&gt; &lt;p&gt;一日聚餐，席间A 君说：“你们这个不行啊，老是给我推娱乐八卦，应该多给我推荐一些行业观点、深度分析之类的内容。” &lt;/p&gt; &lt;p&gt;B 君道：“好好好，把你的用户ID 给我，我查询一下，看看推荐算法是不是有什么问题。”半晌，剧情反转。 &lt;/p&gt; &lt;p&gt;B 君朗朗道：“下午 1 点半，刷新一次，给你推荐了 3 条行业资讯、2 条体育资讯、1 条娱乐八卦，你点了娱乐八卦。下午2 点半，刷新三次，你点击了 1 条行业资讯、3 条娱乐八卦。下午 3 点，刷新……” &lt;/p&gt; &lt;p&gt;众人相视爆笑，终结谈话，定论：“本我超越了超我。” &lt;/p&gt; &lt;p&gt;想象一下，当一篇八卦新闻和一篇深度分析同时出现，你会做出怎样的选择？站在马斯洛需求模型的金字塔前，本我制造了足够大的需求。你点击一篇娱乐八卦，很有可能是本能驱动的下意识行为。你更多地点击娱乐八卦又给了推荐系统对于此类内容更强的反馈信息，从而增加对此类内容的推荐。如果从推荐系统的点击预估角度看，更接地气的内容超过高大上的内容几乎是必然的。 &lt;/p&gt; &lt;p&gt;消费八卦新闻的确帮助你打发了时间、给你制造了欢愉，但你偶尔的“超我”自觉自省，却对这种内容推荐做出了格调低劣的判断。这也是我们在用户访谈过程当中常会碰到的一种情况：就像在一个个夜里，你一遍遍地跟自己说要早睡，却一次次刷着王者荣耀直到深夜。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;3.更平衡的产品设计&lt;/strong&gt; &lt;/p&gt; &lt;p&gt;尽管推荐技术存在一定局限，尽管用户反馈总是会表现出泛娱乐化的趋势，但一个理想态的产品是不应该仅以点击为导向、唯短期数据而论的，我们能够做出的产品设计不应该止步于此。 &lt;/p&gt; &lt;p&gt;假如我们在点击一篇内容重复度很高的娱乐八卦和点击一篇行业深度分析之间，倾向于认为后者更有价值，那就需要找到更好的方式来衡量一次阅读的价值，从而引导推荐系统的分发流程，比如： &lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;内容稀缺度：越垂直的内容越稀缺，小类目下的内容点击可能比大类目下的内容点击更有价值。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;作者角度：从全局来看，每个垂直赛道都会跑出有广泛知名度的内容品牌，它们的内容往往更适合广泛传播；从用户个体来看，某些用户会对内容来源而非内容类目更加敏感，这就需要放大作者的相似度以降低内容的相似度。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;互动行为：不同的行为代表了用户不同的意图。阅读行为仅仅是一种个体行为，点赞、评论都代表了用户对内容更感兴趣，而分享则意味着用户愿意为之传播和背书，可能权重更大。 &lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;不同角度的分析给我们提供了量化点击价值的方式，让那些我们更偏好的内容有可能获得更多的曝光量和展示量。从现实的角度来看，内容供给和内容消费一定会是金字塔结构的，越基础层的内容越具有消费规模。但作为理想的现实主义者，我们或许可以在可控的范围内，去尝试点击率和理想态的平衡。&lt;/p&gt;
      &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>读书</category>
      <guid isPermaLink="true">https://itindex.net/detail/58370-%E6%8E%A8%E8%8D%90%E7%AE%97%E6%B3%95-%E7%94%A8%E6%88%B7-%E6%97%B6%E9%97%B4</guid>
      <pubDate>Sun, 13 May 2018 16:22:05 CST</pubDate>
    </item>
    <item>
      <title>从什么时候起，你玩游戏开始考虑时间成本了？</title>
      <link>https://itindex.net/detail/58250-%E6%B8%B8%E6%88%8F-%E8%80%83%E8%99%91-%E6%97%B6%E9%97%B4</link>
      <description>&lt;p&gt;  &lt;img alt="" src="https://images2018.cnblogs.com/news/66372/201804/66372-20180413234635116-879170185.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;文/Oracle &lt;/p&gt;
 &lt;p&gt;来源：游戏研究社&lt;/p&gt;
 &lt;p&gt;作为大龄玩家的你，感同身受了吗？&lt;/p&gt;
 &lt;p&gt;我有个很活跃的微信群，里面都是一些游戏行业的老前辈，平时大家就是聊天打屁，说说行业或各种生活话题。群里有个梗和今天我要说的主题有关。&lt;/p&gt;
 &lt;p&gt;有位群友——为了保护当事人的隐私，我们叫他高先生——很早就加入游戏行业，现在家庭幸福，有一个上小学的儿子。&lt;/p&gt;
 &lt;p&gt;高先生当年也是一位游戏热爱者，涉猎广泛，直到现在即使工作繁忙，也会抽时间玩游戏。但有个大名鼎鼎的“神作”，群友推荐了很多次让他去玩，他都一直不玩，这个游戏叫《最后的生还者》，下文我们用 TLOU 指代。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://images2018.cnblogs.com/news/66372/201804/66372-20180413234635124-1678478604.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;TLOU 是个多好的游戏啊，玩法、交互、演出、剧情，不是满分也是 9 分，高先生怎会不知？但他就是不玩，哪怕去玩通了一些独立游戏或 PS VR 游戏，都没碰 TLOU。&lt;/p&gt;
 &lt;p&gt;群友因此吐槽好多次，时不时就有这样的对话：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://images2018.cnblogs.com/news/66372/201804/66372-20180413234635112-1259025552.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://images2018.cnblogs.com/news/66372/201804/66372-20180413234635103-1481048813.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://images2018.cnblogs.com/news/66372/201804/66372-20180413234635104-127392940.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#19978;&amp;#22270;&amp;#22343;&amp;#20026;&amp;#32676;&amp;#21451;&amp;#20043;&amp;#38388;&amp;#30340;&amp;#29609;&amp;#31505;&amp;#35805;&amp;#65292;&amp;#21247;&amp;#19978;&amp;#32434;&amp;#19978;&amp;#32447;" src="https://images2018.cnblogs.com/news/66372/201804/66372-20180413234635113-676184201.png"&gt;&lt;/img&gt;  &lt;br /&gt;上图均为群友之间的玩笑话，勿上纲上线&lt;/p&gt;
 &lt;p&gt;就这样持续了差不多一年，TLOU 就成了一个梗。当事人为了反击群友的吐槽，还专门做了个表情。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://images2018.cnblogs.com/news/66372/201804/66372-20180413234635198-1631073999.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;当然大家也就开开玩笑，哈哈一笑就过去了，不会真的怎样怎样。但前几天我突然想起这茬，就找高先生闲聊了几句，好奇问了问不玩 TLOU 的原因到底是什么。&lt;/p&gt;
 &lt;p&gt;高先生第一句话就说：&lt;/p&gt;
 &lt;p&gt;“这件事是中年人的悲哀……”&lt;/p&gt;
 &lt;p&gt;高先生之所以迟迟不玩 TLOU，主要和他的时间管理有关。因为有家庭、孩子和工作的关系，时间被“等量地切碎了”，要经营家庭，要工作充电，哪怕有所谓的“闲暇时间”，也未必能用来打游戏，实际的游戏时间又少又短。&lt;/p&gt;
 &lt;p&gt;“TLOU 我了解一下，需要至少 15 个小时。你知道我这次过年，才抽出时间把暗黑 3 打完吗？我当年特别喜欢暗黑2，六年前暗黑 3 发售，我一直扛到今年春节，才终于抽了差不多2~3 天。啥也不干，打穿了它，而且还是在媳妇带娃回老家的期间。”&lt;/p&gt;
 &lt;p&gt;“我没有一个整块的 15 小时能玩 TLOU……虽然我知道这个游戏特别好，而且我也一直认同单机游戏比网络游戏更像一个艺术类作品。但游戏是娱乐的一部分，娱乐是生活的调节剂，我们热爱游戏，是为了让我们热爱生活，岂能本末倒置……”&lt;/p&gt;
 &lt;p&gt;高先生说的理由和我预想中差不多，虽然从理论上说，如果有心，闲暇时间再碎，只要规划合理，都能通掉一款游戏。但现实情况往往是，有了一点闲暇时间——比如 1 小时左右，拿起手柄重新融入一个强调剧情、流程连贯性非常高的游戏，是一个不算低的门槛，更别说刚玩了一会还得被强制打断，再拿起来就比较难了。&lt;/p&gt;
 &lt;p&gt;比起这个，高先生宁愿去玩一些能短时间内带来比较完整体验的游戏。去年他通关了《ABZU》，这是个模仿《风之旅人》的艺术类游戏，是《风之旅人》的艺术总监自立门户做的，玩起来火候欠了一些。但高先生有天下午正好有空，把它兴致勃勃地打通了，在朋友圈大赞。&lt;/p&gt;
 &lt;p&gt;我特别能理解这种感受：在这个有无数游戏可以选择的时代，能让你有兴趣打到通关的那个游戏，对你来说就是最好的。&lt;/p&gt;
 &lt;p&gt;这种选择，可能不能为狂热的游戏玩家所理解，但对于很多也会玩主机或 PC 单机的普通玩家来说，“估量一下时间成本”，已经越来越成为参加工作和拥有家庭之后的习惯性动作。&lt;/p&gt;
 &lt;p&gt;过去我在游戏杂志上看过一位编辑的自述，说自己打 FF12，打到一半忘了打，就那么放着了，再没力气拿起来。但与此同时，自己的实况却每天都在踢，一年下来，也有几百个小时了。&lt;/p&gt;
 &lt;p&gt;我当时很不理解这种行为——明明有几百个小时，都能通关很多游戏了。为啥要耗在一个游戏上？&lt;/p&gt;
 &lt;p&gt;后来我逐渐明白，“玩一个游戏省事，玩不同游戏费劲”。&lt;/p&gt;
 &lt;p&gt;每一名游戏玩家，都经历过一段不计成本的游戏时光。在这个时期，游戏只分为“好玩”、“不好玩”和“超好玩”。只要喜欢，就会不计时间地投入进去，除非受到人为和金钱的阻碍，不然不存在“明明喜欢玩却还是没玩”的问题。&lt;/p&gt;
 &lt;p&gt;但随着玩家长大，离开学校，进入社会，参加工作。突然，从某个时刻起，你会对着一个明知道很好的游戏犯嘀咕：&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;“这游戏真好啊，就是太耗时间了。”&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;随着这个念头，过去与现在轰然裂成两个世界。你再也不是从前那个无忧无虑的你，游戏对你的吸引力，也再也没有过去那么强。&lt;/p&gt;
 &lt;p&gt;就算从理性层面知道一个游戏特别优秀，但看到需要投入的时间和精力时，心里就会打个算盘。&lt;/p&gt;
 &lt;p&gt;然后可能就算了。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://images2018.cnblogs.com/news/66372/201804/66372-20180413234635210-1480402239.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;为什么会这样？&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;不是不爱了，而是出现了太多东西，吸引你的注意力，争夺你的时间。&lt;/p&gt;
 &lt;p&gt;80 后或者 90 后玩家都记得小时候玩游戏废寝忘食的时光，除了年轻气盛无忧无虑，还有一个重要的原因是，当年没有那么多抢占注意力、争夺时间的娱乐方式。&lt;/p&gt;
 &lt;p&gt;游戏作为一种娱乐方式，曾经有一种重要的作用叫“打发时间 ”。但现代的问题是，娱乐的方式越来越多，可用来打发的时间，却越来越少了。&lt;/p&gt;
 &lt;p&gt;这时候，从“时间争夺效率”的角度说，需要大块时间才能完整体验的传统游戏，在竞争上其实处于弱势。这些游戏要和电影、综艺竞争，要和今日头条和公众号里的标题党竞争，要和微博上 140 个字的段子竞争，要和秒拍上的搞笑视频竞争，要和抖音微视快手的短视频竞争，虽然短视频行业已经快要被捶死了。&lt;/p&gt;
 &lt;p&gt;更关键的是，它们要和更短更刺激的同类竞争，和那些几十分钟甚至十几分钟就能打一盘的 FPS 对抗、MOBA 和吃鸡游戏争夺时间。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;所有的娱乐产品，都是一门时间和注意力的生意。&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;从时间的纬度看过去，掌机的死，不在于人们觉得除了手机外多带一个设备会很麻烦，而是已经没有时间分给掌机。&lt;/p&gt;
 &lt;p&gt;NS 的火，也不是因为两个满分级别的游戏，虽然他们的确带了不少销量。但归根结底，是 NS 拓展了主机游戏的使用时间。那些原本不能来玩主机的时间，被 NS 利用了起来，这大大增加了 NS 的竞争力。&lt;/p&gt;
 &lt;p&gt;谁能争取更多时间，谁就有更大胜算。但游戏，说实在的，需要争取的时间太多了。&lt;/p&gt;
 &lt;p&gt;因为时间少而游戏太多，这些年还出现了“都花钱买游戏了，为啥还要花时间玩”的流行梗。这本身对应着一个比较反常的供需关系，你很少会看到“囤了太多电影看不过来”“下了太多音乐听不过来”的问题。它们总能很快地消化掉，因为消化他们不费劲。&lt;/p&gt;
 &lt;p&gt;你是否还发现了一个现象，只有当独占 3A 大作发售时，你的 PSN 好友才会齐刷刷在线（比如像过几天的战神），而在 3A 的空档期，好友列表门可罗雀？&lt;/p&gt;
 &lt;p&gt;其实对一名普通玩家来说，一年十几款游戏玩下来，已经撑得饱饱的。而一年光 3A 大作都不至十几款，光玩这些都玩不过来——事实上，你看很多很多游戏社区讨论得虽然热闹，但翻来覆去也就是那些 3A 游戏。&lt;/p&gt;
 &lt;p&gt;玩家的游戏时间越来越不够，还导致了一个现象：这些年质量不上不下的“中型游戏”大批大批地死掉。这个转折点差不多发生在 2006 年左右，除了平台更新换代导致游戏销量无法抵扣更高的开发成本外，还有个原因是，这个时期的各种互联网基础建设开始发达起来，Youtube、Twitter、Facebook 都是在这个时期前后做起来的。用户能轻易找到打发时间的方式，而不用去玩一些6~7 分的游戏。&lt;/p&gt;
 &lt;p&gt;于是，虽然现在游戏已经是消费成本最低的娱乐之一，但玩游戏这个行为本身，却成为一种越来越奢侈的行为。&lt;/p&gt;
 &lt;p&gt;作为大龄玩家的你，感受到了吗？&lt;/p&gt; &lt;p&gt;  &lt;a href="http://news.cnblogs.com/n/594221/" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt; &lt;img alt="" height="1" src="http://news.cnblogs.com/news/rssclick.aspx?id=594221" width="1"&gt;&lt;/img&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/58250-%E6%B8%B8%E6%88%8F-%E8%80%83%E8%99%91-%E6%97%B6%E9%97%B4</guid>
      <pubDate>Sat, 14 Apr 2018 23:38:57 CST</pubDate>
    </item>
  </channel>
</rss>

