您的位置:首页 > 软件问答

交叉日crossdays(School Days出前传,中文译名又是难点,网友:我词穷了)

导读交叉日crossdays文章列表:1、School Days出前传,中文译名又是难点,网友:我词穷了2、击肘代替接吻……疫情下,美国线下戏剧这么演3、RealNVP与Glow:流模型的传承与升华4、SQL数据

交叉日crossdays文章列表:

交叉日crossdays(School Days出前传,中文译名又是难点,网友:我词穷了)

School Days出前传,中文译名又是难点,网友:我词穷了

“别笑诚哥si得早,成哥笑你见识少”。作为一部恋爱向且具有一定冒险元素的作品,《日在校园》可谓是大名鼎鼎,相信对于各位阿宅绅士们来说,那更是不陌生了。

对于网友而言,一部好的作品,不仅体现在剧情上,名称亦是如此。就如小编说的《日在校园》一样,如若将原作名《School Days》照搬,估计在国内热度会降下去不少,可见名字的重要性。

正是因为《School Days》如此火爆,所以它在Galgame领域里影响极大,且衍生的动漫作品对后继的校园类动漫产生了深远的影响。

一般来说,一部人气超高的作品,在第一部制作完成后,定会推出续作或是与其相关的外传等作品,以此榨取该部作品最后的价值。这一点不仅仅体现在ACG界,像其他领域,大都是如此。

而《School Days》自然也是如此,像以《School Days》为基础推出的《Summer Days》、《Cross Days》、《Island Days》等后继作品不仅让Days系列的知名度大大提高,也让编剧拥有更大的开阔方向,从而致使有吸引力的作品一部接着一部。

这不,继上面这些作品后,《School Days》的前传《Mother Days》也正准备上线。但是这时问题又来了,根据以往的作品译名,《Mother Days》这部作品如何取出一个优质的名称,成为了难点。

大家也都知道,《School Days》正常翻译过来的名字应该是“学院的日子”,这样的名字也是十分符合作品标签本身,但基于我国文化博大精深,再受到广大漫迷们的影响,最终《日在校园》这个译名被敲定下来。

这个译名无论是从剧情本身还是诚哥的伟大“壮举”方面上来看,都是十分的贴切,可以说十分到位且非常精髓,毫无违和感,能有这么大的吸引力自然也就不奇怪了。

但要说到以《School Days》中各个女主角母亲为主要角色的《Mother Days》,那译名可就十分头疼了。如果借鉴以往的续作译名来命名,又有点不太适用。

原因无他,主要是之前的《Summer Days》可以翻译成《ri在夏天》、《Cross Days》可以翻译成《交叉之ri》,但《Mother Days》显然有些与众不同,如若按照以往惯例再来翻译,肯定是行不通了。

因此有不少网友献上了自己心中的理想译名,像什么“太阳妈妈”,“母亲之日”,可无一例外,都没有表现出《Mother Days》的真正精髓。

虽说“母亲之日”这个译名看起来还算靠谱,但小编总觉得不贴合作品的主题,且总感觉和母亲节有关系。而“太阳妈妈”更是非常幼稚,听起来简直有种“花园宝宝”的既视感,总感觉在年龄上有些不对路。

《Mother Days》以《School Days》为基础,所以在剧情及人设上肯定与《School Days》息息相关。毕竟从《日在校园》中各位母亲大人的人设上来看,众多阿宅绅士们都觉得有意思,更别说年轻版的母亲大人了。

然而没有一个靠谱的译名,要在国内引起反响始终是有难度的,可要翻译出之前这般十分贴切的译名,实在是让人抓耳挠腮,不少网友表示已经词穷了。

对于《Mother Days》,各位小伙伴觉得最适合的译名该是什么呢?欢迎在下方留言。喜欢润界本地化,别忘了点击关注哟!

击肘代替接吻……疫情下,美国线下戏剧这么演

尽管剧院恢复到重启常态还需要一段时间,但全球戏剧人正在想尽一切办法在观众面前进行现场表演,以让剧院继续生存下去。他们在严格遵守各地政府保持不同社交距离的前提下,为保证演出顺利进行,引入汽车影院的形式将舞台搬到户外、口罩变成一种表演道具、露天戏剧中“接吻”的部分用“击肘”代替等,这些为应对疫情寻找的多种戏剧表演新方式,正呼应着戏剧可以互动、创作流动的独特魅力。

“汽车”剧院

作品:《蚱蜢》(邦波特剧院)

图来自官网。下同

位于科罗拉多州的邦波特剧院(BUNTPORT THEATER),疫情期间决定走出剧院在户外进行创作。剧院主创从免下车的汽车影院中汲取了灵感,推出了一部名为《蚱蜢》(The Grasshoppers)的作品。该剧由四位演员穿着绿色连体衣,在邦波特剧院前的草坪上,为汽车里的观众表演了一个以“隔离”为主题的戏剧作品,观众在车内透过挡风玻璃观看表演,现场对话和音乐通过手机和车内收音机收听。

《蚱蜢》四个演员

《蚱蜢》全剧仅35分钟,每场名额非常有限,因此观众必须通过网络提前预订。演出门票采用捐赠形式,根据车里的观众人数,尽自己所能支付票款,如果观众遇到经济困难可免费观看。剧院还特别提示,观众务必戴上口罩,汽车因并排停放,以防观众打开车窗交叉感染。观众可以使用剧院的卫生间,但他们希望观众始终呆在车里,因为这是一个非常短的节目,表演者也不会靠近观众。

“摘口罩”成表演内容

作品:《拍摄奥基夫》(隐形剧院)

在亚利桑那州图森的隐形剧院(Invisible Theater),6月23日上演了一部名为《拍摄奥基夫》(Filming O’Keefe)四个角色的戏剧作品。《拍摄奥基夫》本应是隐形剧院2019-2020这季的最后一场演出。当新冠肺炎疫情来袭,三月份关闭了该州的大部分剧院时,作品的舞美、灯光和音乐已经完成。这部作品每场演出只接受22名观众入场,剧院容量的25%,座位在保持社交距离的同时,所有观众必须戴上口罩。

演出中,演员不会进行身体上的互动,而且也是穿着他们在舞台上的衣服来到剧院,不使用更衣室。角色第一次出现在舞台上时他们戴着口罩,之后口罩将在演出过程中摘掉,每个人在舞台上都会占据一个独立空间,不会进行肢体互动。剧院艺术总监苏珊·克拉森表示:“现场戏剧与在电脑屏幕上看到的东西有很大的不同。我们隐形剧院的名字来源于演员和观众之间流动的无形能量,即使22个观众戴着口罩,这种能量也是无比强大的。”

接吻用“击肘”代替

代表作品:《灰姑娘》(阿罕布拉晚餐剧院)

演员都戴着手套。

阿罕布拉晚餐剧院(Alhambra Dinner Theater)传统剧目《灰姑娘》依然还在上演,但表演内容因疫情出现了改变。在演出的跳舞环节中,表演者都戴着手套,背对着对方。即使最后灰姑娘和王子的吻,男女主角都要用“击肘”替代。

目前这家剧院只出售50%的座位,在座位与座位之间还安装了有机玻璃。观看演出前,《灰姑娘》还加设了用餐环节,每个预订座位之间至少保持2米距离。在整个建筑中,客人会清楚地看到测量的距离标记在地板上,以避免拥挤。

台上演员保持2米距离

代表作品:《丘比特之箭》(脚灯剧院)

在缅因州的脚灯剧院(The Footlights Theater),按照剧院传统,整个夏天只会有一场演出,今年的演出名为《丘比特之箭》(Cupid’s Arrow),由两位演员出演。编剧为这部作品重写了《丘比特之箭》的结尾,想用幽默缓解新冠疫情带给人们的阴郁。

《丘比特之箭》允许两个表演者保持2米的距离,并在有机玻璃后面进行表演。观众减少到25名,第一排观众离演员和舞台2米远。演员有自己的剧院入口,个人将分别从舞台的两边不同台口登场。

控制观众不超过6人

代表作品:《道林·格雷的画像》(消防站剧院)

直播中的《道林·格雷的画像》

在美国弗吉尼亚州里士满的消防站剧院(Firehouse Theatre),舞台上一位孤独的演员,正在给两名观众表演《道林·格雷的画像》。这部作品改编自王尔德1891年的哥特故事《欲望、死亡和短暂》。这部作品在能容纳99人的剧场演出,但建议一场观看人数为2-6人。每场演出的票价分为30美元及随意金额(鼓励捐赠更多金额)。演出除了可在现场观看外,还可通过邮件预约方式发送给付费观众。

露天演出

代表作品:《耶稣基督在地球上的最后日子》(尤里卡温泉圆形露天剧场)

在阿肯色州尤里卡温泉拥有4000个座位的圆形露天剧场里,每晚大约有150名演员在为数百名持票观众,重现上演了几十年的经典戏剧《耶稣基督在地球上的最后日子》( Jesus Christ's Last Days On Earth),全剧1小时45分钟,重现了耶稣基督生命最后一周发生的故事。它一直是美国最受欢迎的户外戏剧演出之一,自1968年7月首演以来,世界上超过700万人欣赏过这部作品。

通常情况下,这部作品会于耶稣受难日(4月2日)在圆形露天剧场上演,但是今年由于新冠疫情的影响,演出推迟。在疫情期间演出这部作品比平时具有挑战性,上座率的下降,正好反映了旅游业的衰退。根据目前的演出时间表,该演出将持续到今年10月,其中在7月每周将演出四个晚上,但在其他月份每周只演出一晚,每晚演出后座位都会进行全面消毒。

新京报记者 刘臻

编辑 田偲妮 校对 刘军

来源:新京报网

RealNVP与Glow:流模型的传承与升华

精巧的flow

不得不说,flow 模型是一个在设计上非常精巧的模型。总的来看,flow 就是想办法得到一个 encoder 将输入 x 编码为隐变量 z,并且使得 z 服从标准正态分布。得益于 flow 模型的精巧设计,这个 encoder 是可逆的,从而我们可以立马从 encoder 写出相应的 decoder(生成器)出来,因此,只要 encoder 训练完成,我们就能同时得到 decoder,完成生成模型的构建。

为了完成这个构思,不仅仅要使得模型可逆,还要使得对应的雅可比行列式容易计算,为此,NICE 提出了加性耦合层,通过多个加性耦合层的堆叠,使得模型既具有强大的拟合能力,又具有单位雅可比行列式。就这样,一种不同于 VAE 和 GAN 的生成模型——flow 模型就这样出来了,它通过巧妙的构造,让我们能直接去拟合概率分布本身。

待探索的空间

NICE 提供了 flow 模型这样一种新的思路,并完成了简单的实验,但它同时也留下了更多的未知的空间。flow 模型构思巧妙,相比之下,NICE 的实验则显得过于粗糙:只是简单地堆叠了全连接层,并没有给出诸如卷积层的用法,论文虽然做了多个实验,但事实上真正成功的实验只有 MNIST,说服力不够。

因此,flow 模型还需要进一步挖掘,才能在生成模型领域更加出众。这些拓展,由它的“继承者”RealNVP 和 Glow 模型完成了,可以说,它们的工作使得 flow 模型大放异彩,成为生成模型领域的佼佼者。

RealNVP

这部分我们来介绍 RealNVP 模型,它是 NICE 的改进,来自论文 Density estimation using Real NVP [1]。它一般化了耦合层,并成功地在耦合模型中引入了卷积层,使得可以更好地处理图像问题。更进一步地,它还提出了多尺度层的设计,这能够降低计算量,通过还提供了强大的正则效果,使得生成质量得到提升。至此,flow 模型的一般框架开始形成。

后面的 Glow 模型基本上沿用了 RealNVP 的框架,只是对部分内容进行了修改(比如引入了可逆 1x1 卷积来代替排序层)。不过值得一提的是,Glow 简化了 RealNVP 的结构,表明 RealNVP 中某些比较复杂的设计是没有必要的。因此本文在介绍 RealNVP 和 Glow 时,并没有严格区分它们,而只是突出它们的主要贡献。

仿射耦合层

其实 NICE 和 RealNVP 的第一作者都是 Laurent Dinh,他是 Bengio 的博士生,他对 flow 模型的追求和完善十分让我钦佩。在第一篇 NICE 中,他提出了加性耦合层,事实上也提到了乘性耦合层,只不过没有用上;而在 RealNVP 中,加性和乘性耦合层结合在一起,成为一个一般的“仿射耦合层”。

这里的 s,t 都是 x1 的向量函数,形式上第二个式子对应于 x2 的一个仿射变换,因此称为“仿射耦合层”。

仿射耦合的雅可比矩阵依然是一个三角阵,但对角线不全为 1,用分块矩阵表示为:

很明显,它的行列式就是 s 各个元素之积。为了保证可逆性,一般我们约束 s 各个元素均大于零,所以一般情况下,我们都是直接用神经网络建模输出 log s,然后取指数形式

注:从仿射层大概就可以知道 RealNVP 的名称来源了,它的全称为“real-valued non-volume preserving”,强行翻译为“实值非体积保持”。相对于加性耦合层的行列式为 1,RealNVP 的雅可比行列式不再恒等于 1,而我们知道行列式的几何意义就是体积(请参考《新理解矩阵5:体积=行列式》[2]),所以行列式等于 1 就意味着体积没有变化,而仿射耦合层的行列式不等于 1 就意味着体积有所变化,所谓“非体积保持”。

随机打乱维度

在 NICE 中,作者通过交错的方式来混合信息流(这也理论等价于直接反转原来的向量),如下图(对应地,这里已经换为本文的仿射耦合层图示):

▲ NICE通过交叉耦合,充分混合信息

而 RealNVP 发现,通过随机的方式将向量打乱,可以使信息混合得更加充分,最终的 loss 可以更低,如图:

▲ RealNVP通过随机打乱每一步输出的整个向量,使得信息混合得更充分均匀

这里的随机打乱,就是指将每一步 flow 输出的两个向量 h1,h2 拼接成一个向量 h,然后将这个向量重新随机排序。

引入卷积层

RealNVP 中给出了在 flow 模型中合理使用卷积神经网络的方案,这使得我们可以更好地处理图像问题,并且减少参数量,还可以更充分发挥模型的并行性能。

注意,不是任意情况下套用卷积都是合理的,用卷积的前提是输入(在空间维度)具有局部相关性。图像本身是具有局部相关性的,因为相邻之间的像素是有一定关联的,因此一般的图像模型都可以使用卷积。

但是我们注意 flow 中的两个操作:

1. 将输入分割为两部分 x1,x2,然后输入到耦合层中,而模型 s,t 事实上只对 x1 进行处理;

2. 特征输入耦合层之前,要随机打乱原来特征的各个维度(相当于乱序的特征)。这两个操作都会破坏局部相关性,比如分割操作有可能割裂原来相邻的像素,随机打乱也可能将原来相邻的两个像素分割得很远。

所以,如果还要坚持使用卷积,就要想办法保留这种空间的局部相关性。我们知道,一幅图像有三个轴:高度(height)、宽度(width)、通道(channel),前两个属于空间轴,显然具有局部相关性,因此能“搞”的就只有“通道”轴。

为此,RealNVP 约定分割和打乱操作,都只对“通道”轴执行。也就是说,沿着通道将输入分割为 x1,x2 后,x1 还是具有局部相关性的,还有沿着通道按着同一方式打乱整体后,空间部分的相关性依然得到保留,因此在模型 s,t 中就可以使用卷积了。

▲ 沿着通道轴进行分割,不损失空间上的局部相关性

▲ 沿着空间轴交错(棋盘)分割,也是一种保持空间局部相关性的方案

注:在 RealNVP 中,将输入分割为两部分的操作称为 mask,因为这等价于用 0/1 来区别标注原始输入。除了前面说的通过通道轴对半分的 mask 外,RealNVP 事实上还引入了一种空间轴上的交错 mask,如上图的右边,这种 mask 称为棋盘式 mask(格式像国际象棋的棋盘)。

这种特殊的分割也保留了空间局部相关性,原论文中是两种 mask 方式交替使用的,但这种棋盘式 mask 相对复杂,也没有什么特别明显的提升,所以在 Glow 中已经被抛弃。

不过想想就会发现有问题。一般的图像通道轴就只有三维,像 MNIST 这种灰度图还只有一维,怎么分割成两半?又怎么随机打乱?为了解决这个问题,RealNVP 引入了称为 squeeze 的操作,来让通道轴具有更高的维度。

其思想很简单:直接 reshape,但 reshape 时局部地进行。具体来说,假设原来图像为 h×w×c 大小,前两个轴是空间维度,然后沿着空间维度分为一个个 2×2×c 的块(这个 2 可以自定义),然后将每个块直接 reshape 为 1×1×4c,也就是说最后变成了 h/2×w/2×4c。

▲ squeeze操作图示,其中2x2的小区域可以换为自定义大小的区域

有了 squeeze 这个操作,我们就可以增加通道轴的维数,但依然保留局部相关性,从而我们前面说的所有事情都可以进行下去了,所以 squeeze 成为 flow 模型在图像应用中的必备操作。

多尺度结构

除了成功地引入卷积层外,RealNVP 的另一重要进展是加入了多尺度结构。跟卷积层一样,这也是一个既减少了模型复杂度、又提升了结果的策略。

▲ RealNVP中的多尺度结构图示

多尺度结构其实并不复杂,如图所示。原始输入经过第一步 flow 运算(“flow 运算”指的是多个仿射耦合层的复合)后,输出跟输入的大小一样,这时候将输入对半分开两半 z1,z2(自然也是沿着通道轴),其中 z1 直接输出,而只将 z2 送入到下一步 flow 运算,后面的依此类推。比如图中的特例,最终的输出由 z1,z3,z5 组成,总大小跟输入一样。

多尺度结构有点“分形”的味道,原论文说它启发于 VGG。每一步的多尺度操作直接将数据尺寸减少到原来的一半,显然是非常可观的。但有一个很重要的细节,在 RealNVP 和 Glow 的论文中都没有提到,我是看了源码才明白的,那就是最终的输出 [z1,z3,z5] 的先验分布应该怎么取?按照 flow 模型的通用假设,直接设为一个标准正态分布?

事实上,作为不同位置的多尺度输出,z1,z3,z5 的地位是不对等的,而如果直接设一个总体的标准正态分布,那就是强行将它们对等起来,这是不合理的。最好的方案,应该是写出条件概率公式:

由于 z3,z5 是由 z2 完全决定的,z5 也是由 z4 完全决定的,因此条件部分可以改为:

RealNVP 和 Glow 假设右端三个概率分布都是正态分布,其中 p(z1|z2) 的均值方差由 z2 算出来(可以直接通过卷积运算,这有点像 VAE),p(z3|z4) 的均值方差由 z4 算出来,p(z5) 的均值方差直接学习出来。

显然这样的假设会比简单认为它们都是标准正态分布要有效得多。我们还可以换一种表述方法:上述的先验假设相当于做了如下的变量代换:

然后认为 [ẑ1,ẑ3,ẑ5] 服从标准正态分布。同 NICE 的尺度变换层一样,这三个变换都会导致一个非 1 的雅可比行列式,也就是要往 loss 中加入形如

的这一项。

乍看之下多尺度结构就是为了降低运算量,但并不是那么简单。由于 flow 模型的可逆性,输入输出维度一样,事实上这会存在非常严重的维度浪费问题,这往往要求我们需要用足够复杂的网络去缓解这个维度浪费。

多尺度结构相当于抛弃了 p(z) 是标准正态分布的直接假设,而采用了一个组合式的条件分布,这样尽管输入输出的总维度依然一样,但是不同层次的输出地位已经不对等了,模型可以通过控制每个条件分布的方差来抑制维度浪费问题(极端情况下,方差为 0,那么高斯分布坍缩为狄拉克分布,维度就降低 1),条件分布相比于独立分布具有更大的灵活性。而如果单纯从 loss 的角度看,多尺度结构为模型提供了一个强有力的正则项(相当于多层图像分类模型中的多条直连边)。

Glow

整体来看,Glow 模型在 RealNVP 的基础上引入了 1x1 可逆卷积来代替前面说的打乱通道轴的操作,并且对 RealNVP 的原始模型做了简化和规范,使得它更易于理解和使用。

■ 论文 | https://www.paperweekly.site/papers/2101

■ 博客 | https://blog.openai.com/glow/

■ 源码 | https://github.com/openai/glow

可逆1x1卷积

这部分介绍 Glow 的主要改进工作:可逆 1x1 卷积。

置换矩阵

可逆 1x1 卷积源于我们对置换操作的一般化。我们知道,在 flow 模型中,一步很重要的操作就是将各个维度重新排列,NICE 是简单反转,而 RealNVP 则是随机打乱。不管是哪一种,都对应着向量的置换操作。

事实上,对向量的置换操作,可以用矩阵乘法来描述,比如原来向量是 [1,2,3,4],分别交换第一、二和第三、四两个数,得到 [2,1,4,3],这个操作可以用矩阵乘法来描述:

其中右端第一项是“由单位矩阵不断交换两行或两列最终得到的矩阵”称为置换矩阵。

一般化置换

既然这样,那很自然的想法就是:为什么不将置换矩阵换成一般的可训练的参数矩阵呢?所谓 1x1 可逆卷积,就是这个想法的结果。

注意,我们一开始提出 flow 模型的思路时就已经明确指出,flow 模型中的变换要满足两个条件:一是可逆,二是雅可比行列式容易计算。如果直接写出变换:

那么它就只是一个普通的没有 bias 的全连接层,并不能保证满足这两个条件。为此,我们要做一些准备工作。首先,我们让 h 和 x 的维度一样,也就是说 W 是一个方阵,这是最基本的设置;其次,由于这只是一个线性变换,因此它的雅可比矩阵就是

,所以它的行列式就是 det W,因此我们需要把 −log |det W| 这一项加入到 loss 中;最后,初始化时为了保证 W 的可逆性,一般使用“随机正交矩阵”初始化。

利用LU分解

以上做法只是一个很基本的解决方案,我们知道,算矩阵的行列式运算量特别大,还容易溢出。而 Glow 给出了一个非常巧妙的解决方案:LU 分解的逆运用。具体来说,是因为任意矩阵都可以分解为:

其中 P 是一个置换矩阵,也就是前面说的 shuffle 的等价矩阵;L 是一个下三角阵,对角线元素全为 1;U 是一个上三角阵。这种形式的分解称为 LU 分解。如果知道这种矩阵的表达形式,显然求雅可比行列式是很容易的,它等于:

也就是 U 的对角线元素的绝对值对数之和。既然任意矩阵都可以分解成 (7) 式,我们何不直接设W的形式为 (7) 式?这样一来矩阵乘法计算量并没有明显提升,但求行列式的计算量大大降低,而且计算起来也更为容易。

这就是 Glow 中给出的技巧:先随机生成一个正交矩阵,然后做 LU 分解,得到 P,L,U,固定 P,也固定 U 的对角线的正负号,然后约束 L 为对角线全 1 的下三角阵,U 为上三角阵,优化训练 L,U 的其余参数。

结果分析

上面的描述只是基于全连接的。如果用到图像中,那么就要在每个通道向量上施行同样的运算,这等价于 1x1 的卷积,这就是所谓的可逆 1x1 卷积的来源。事实上我觉得这个名字起得不大好,它本质上就是共享权重的、可逆的全连接层,单说 1x1 卷积,就把它局限在图像中了,不够一般化。

▲ 三种不同的打乱方案最终的loss曲线比较(来自OpenAI博客)

Glow 的论文做了对比实验,表明相比于直接反转,shuffle 能达到更低的 loss,而相比 shuffle,可逆 1x1 卷积能达到更低的 loss。我自己的实验也表明了这一点。

不过要指出的是:可逆 1x1 卷积虽然能降低 loss,但是有一些要注意的问题。第一,loss 的降低不代表生成质量的提高,比如 A 模型用了 shuffle,训练 200 个 epoch 训练到 loss=-50000,B 模型用了可逆卷积,训练 150 个 epoch 就训练到 loss=-55000,那么通常来说在当前情况下 B 模型的效果还不如 A(假设两者都还没有达到最优)。事实上可逆 1x1 卷积只能保证大家都训练到最优的情况下,B 模型会更优。第二,在我自己的简单实验中貌似发现,用可逆 1x1 卷积达到饱和所需要的 epoch 数,要远多于简单用 shuffle 的 epoch 数。

Actnorm

RealNVP 中用到了 BN 层,而 Glow 中提出了名为 Actnorm 的层来取代 BN。不过,所谓 Actnorm 层事实上只不过是 NICE 中的尺度变换层的一般化,也就是 (5) 式提到的缩放平移变换:

其中 μ,σ 都是训练参数。Glow 在论文中提出的创新点是用初始的 batch 的均值和方差去初始化 μ,σ 这两个参数,但事实上所提供的源码并没有做到这一点,纯粹是零初始化。

所以,这一点是需要批评的,纯粹将旧概念换了个新名字罢了。当然,批评的是 OpenAI 在 Glow 中乱造新概念,而不是这个层的效果。缩放平移的加入,确实有助于更好地训练模型。而且,由于 Actnorm 的存在,仿射耦合层的尺度变换已经显得不那么重要了。

我们看到,相比于加性耦合层,仿射耦合层多了一个尺度变换层,从而计算量翻了一倍。但事实上相比加性耦合,仿射耦合效果的提升并不高(尤其是加入了 Actnorm 后),所以要训练大型的模型,为了节省资源,一般都只用加性耦合,比如 Glow 训练 256x256 的高清人脸生成模型,就只用到了加性耦合。

源码分析

事实上 Glow 已经没有什么可以特别解读的了。但是 Glow 整体的模型比较规范,我们可以逐步分解一下 Glow 的模型结构,为我们自己搭建类似的模型提供参考。这部分内容源自我对 Glow 源码的阅读,主要以示意图的方式给出。

模型总图

整体来看,Glow 模型并不复杂,就是在输入加入一定量的噪声,然后输入到一个 encoder 中,最终用“输出的平均平方和”作为损失函数(可以将模型中产生的对数雅可比行列式视为正则项),注意,loss 不是“平方平均误差(MSE)”,而仅仅是输出的平方和,也就是不用减去输入。

▲ Glow模型总图

encoder

下面对总图中的 encoder 进行分解,大概流程为:

▲ encoder流程图

encoder 由 L 个模块组成,这些模块在源码中被命名为 revnet,每个模块的作用是对输入进行运算,然后将输出对半分为两份,一部分传入下一个模块,一部分直接输出,这就是前面说的多尺度结构。Glow 源码中默认 L=3,但对于 256x256 的人脸生成则用到 L=6。

revnet

现在来进一步拆解 encoder,其中 revnet 部分为:

▲ revnet结构图

其实它就是前面所说的单步 flow 运算,在输入之前进行尺度变换,然后打乱轴,并且进行分割,接着输入到耦合层中。如此训练 K 次,这里的 K 称为“深度”,Glow 中默认是 32。其中 actnorm 和仿射耦合层会带来非 1 的雅可比行列式,也就是会改动 loss,在图上也已注明。

split2d

Glow 中的定义的 split2d 不是简单的分割,而是混合了对分割后的变换运算,也就是前面所提到的多尺度输出的先验分布选择。

▲ glow中的split2d并不是简单的分割

对比 (5) 和 (9),我们可以发现条件先验分布与 Actnorm 的区别仅仅是缩放平移量的来源,Actnorm 的缩放平移参数是直接优化而来,而先验分布这里的缩放平移量是由另一部分通过某个模型计算而来,事实上我们可以认为这种一种条件式 Actnorm(Cond Actnorm)。

f

最后是 Glow 中的耦合层的模型(放射耦合层的 s,t),源码中直接命名为 f,它用了三层 relu 卷积:

▲ glow中耦合层的变换模型

其中最后一层使用零初始化,这样就使得初始状态下输入输出一样,即初始状态为一个恒等变换,这有利于训练深层网络。

复现

可以看到 RealNVP 其实已经做好了大部分工作,而 Glow 在 RealNVP 的基础上进行去芜存菁,并加入了自己的一些小修改(1x1 可逆卷积)和规范。但不管怎么样,这是一个值得研究的模型。

Keras版本

官方开源的 Glow 是 TensorFlow 版的。这么有意思的模型,怎么能少得了 Keras 版呢,先奉上笔者实现的 Keras 版:

https://github.com/bojone/flow/blob/master/glow.py

已经 pull request 到 Keras 官方的 examples,希望过几天能在 Keras 的 github 上看到它。

由于某些函数的限制,目前只支持 TensorFlow 后端,我的测试环境包括:Keras 2.1.5 tensorflow 1.2 和 Keras 2.2.0 tensorflow 1.8,均在 Python 2.7 下测试。

效果测试

刚开始读到 Glow 时,我感到很兴奋,仿佛像发现了新大陆一样。经过一番学习后,我发现......Glow 确实是一块新大陆,然而却非我等平民能轻松登上的。

让我们来看 Glow 的 github 上的两个 issue:

How many epochs will be take when training celeba? [3]

The samples we show in the paper are after about 4000 training epochs...

Anyone reproduced the celeba-HQ results in the paper? [4]

Yes we trained with 40 GPU's for about a week, but samples did start to look good after a couple of days...

我们看到 256x256 的高清人脸图像生成,需要训练 4000 个 epoch,用 40 个 GPU 训练了一周,简单理解就是用 1 个 GPU 训练一年...(卒)

好吧,我还是放弃这可望而不可及的任务吧,我们还是简简单单玩个 64x64,不,还是 32x32 的人脸生成,做个 demo 出来就是了。

▲ 用glow模型生成的32x32人脸,150个epoch

▲ 用glow模型生成的cifar10,700个epoch

感觉还可以吧,我用的是 L=3,K=6,每个 epoch 要 70s 左右(GTX1070)。跑了 150 个 epoch,这里的 epoch 跟通常概念的 epoch 不一样,我这里的一个 epoch 就是随机抽取的 3.2 万个样本,如果每次跑完完整的 epoch,那么用时更久。同样的模型,顺手也跑了一下 cifar10,跑了 700 个 epoch,不过效果不大好。就是远看似乎还可以,近看啥都不是的那种。

当然,其实 cifar10 虽然不大(32x32),但事实上生成 cifar10 可比生成人脸难多了(不管是哪种生成模型),我们就跳过吧。话说 64x64 的人脸,我也作死地尝试了一下,这时候用了 L=3,K=10,跑了 200 个 epoch(这时候每个 epoch 要 6 分钟了)。结果..……

▲ 用glow模型生成的64x64人脸,230个epoch

人脸是人脸了,不过看上去更像妖魔脸。看来网络深度和 epoch 数都还不够,我也跑不下去了。

艰难结束

好了,对 RealNVP 和 Glow 的介绍终于可以结束了。本着对 Glow 的兴趣,利用前后两篇文章把三个 flow 模型都捋了一遍,希望对读者有帮助。

总体来看,诸如 Glow 的 flow 模型整体确实很优美,但运算量还是偏大了,训练时间过长,不像一般的 GAN 那么友好。个人认为 flow 模型要在当前以 GAN 为主的生成模型领域中站稳脚步,还有比较长的路子要走,可谓任重而道远呀。

SQL数据分析系列10. 重谈连接

数据与智能 本公众号关注大数据与人工智能技术。由一批具备多年实战经验的技术极客参与运营管理,持续输出大数据、数据分析、推荐系统、机器学习、人工智能等方向的原创文章,每周至少输出7篇精品原创。同时,我们会关注和分享大数据与人工智能行业动态。欢迎关注。

来源 | Learning SQL Generate, Manipulate, and Retrieve Data, Third Edition

作者 | Alan Beaulieu

译者 | Liangchu

校对 | gongyouliu

编辑 | auroral-L

全文共7786字,预计阅读时间35分钟。

第十章 重谈连接

1. 外连接

1.1 左外连接和右外连接

1.2 三路外连接

2. 交叉连接

3. 自然连接

到目前为止,我在第五章中介绍过内连接的概念——你应该已经熟悉了。本章重点介绍连接表的其他方法,包括外连接(outer join)和交叉连接(cross join)。

1. 外连接

迄今为止,前面所有包含多个表的示例中,我们并没有考虑连接条件可能无法与表中所有行匹配的问题。例如,inventory表为每个可供租赁的电影存储一行数据,但是在film表的1000行数据中,只有985行在inventory表中存在一行或多行信息,也就是说,其余42部电影是不能出租的(可能是还没有上映的新片),所以在inventory表中是找不到这些电影的ID的。下面的查询通过连接这两个表来统计每个电影的可用拷贝数:

mysql> SELECT f.film_id, f.title, count(*) num_copies -> FROM film f -> INNER JOIN inventory i -> ON f.film_id = i.film_id -> GROUP BY f.film_id, f.title; --------- ----------------------------- ------------ | film_id | title | num_copies | --------- ----------------------------- ------------ | 1 | ACADEMY DINOSAUR | 8 || 2 | ACE GOLDFINGER | 3 || 3 | ADAPTATION HOLES | 4 || 4 | AFFAIR PREJUDICE | 7 |...| 13 | ALI FOREVER | 4 || 15 | ALIEN CENTER | 6 |...| 997 | YOUTH KICK | 2 || 998 | ZHIVAGO CORE | 2 || 999 | ZOOLANDER FICTION | 5 || 1000 | ZORRO ARK | 8 | --------- ----------------------------- ------------ 958 rows in set (0.02 sec)

虽然你可能期望返回1000行(每个电影一行)数据,但查询只返回了958行。这是因为查询使用内连接,它只返回满足连接条件的行。例如,电影Alice Fantasia(film_id 14)不会出现在结果中,因为它在inventory表中没有相关行记录。

如果希望不论inventory表中有没有相关记录,查询都返回所有1000部电影,可以使用外连接,这实际上使连接条件成为可选条件:

mysql> SELECT f.film_id, f.title, count(i.inventory_id) num_copies -> FROM film f -> LEFT OUTER JOIN inventory i -> ON f.film_id = i.film_id -> GROUP BY f.film_id, f.title; --------- ----------------------------- ------------ | film_id | title | num_copies | --------- ----------------------------- ------------ | 1 | ACADEMY DINOSAUR | 8 || 2 | ACE GOLDFINGER | 3 || 3 | ADAPTATION HOLES | 4 || 4 | AFFAIR PREJUDICE | 7 |...| 13 | ALI FOREVER | 4 || 14 | ALICE FANTASIA | 0 || 15 | ALIEN CENTER | 6 |...| 997 | YOUTH KICK | 2 || 998 | ZHIVAGO CORE | 2 || 999 | ZOOLANDER FICTION | 5 || 1000 | ZORRO ARK | 8 | --------- ----------------------------- ------------ 1000 rows in set (0.01 sec)

如你所见,查询现在返回film表中所有1000行数据,其中42行(包括Alice Fantasia)在num_copies列中的值为0,这表示没有库存(在inventory表中无相关数据)。

以下是对该查询早期版本所做更改的描述:

• 连接定义从内部(inner)更改为左外部(left outer),表示服务器在连接成功的情况下将连接左侧表中的所有行包含进来(在本例中为film表),然后包括连接右侧表中的列(inventory表)。

• num_copies列定义从count(*)更改为count(i.inventory_id),表示统计inventory.inventory_id列的非空值数目。

接下来,让我们删除group by子句并过滤大部分行,以便更清楚地看到内连接和外连接之间的差异。下面是一个使用内连接和过滤条件的查询,该查询仅返回几个电影的行数据:

mysql> SELECT f.film_id, f.title, i.inventory_id -> FROM film f -> INNER JOIN inventory i -> ON f.film_id = i.film_id -> WHERE f.film_id BETWEEN 13 AND 15; --------- -------------- -------------- | film_id | title | inventory_id | --------- -------------- -------------- | 13 | ALI FOREVER | 67 || 13 | ALI FOREVER | 68 || 13 | ALI FOREVER | 69 || 13 | ALI FOREVER | 70 || 15 | ALIEN CENTER | 71 || 15 | ALIEN CENTER | 72 || 15 | ALIEN CENTER | 73 || 15 | ALIEN CENTER | 74 || 15 | ALIEN CENTER | 75 || 15 | ALIEN CENTER | 76 | --------- -------------- -------------- 10 rows in set (0.00 sec)

结果表明,库存中有4份Ali Forever和6份Alien Center的副本(可供租赁)。以下是使用外连接的相同查询:

mysql> SELECT f.film_id, f.title, i.inventory_id -> FROM film f -> LEFT OUTER JOIN inventory i -> ON f.film_id = i.film_id -> WHERE f.film_id BETWEEN 13 AND 15; --------- ---------------- -------------- | film_id | title | inventory_id | --------- ---------------- -------------- | 13 | ALI FOREVER | 67 || 13 | ALI FOREVER | 68 || 13 | ALI FOREVER | 69 || 13 | ALI FOREVER | 70 || 14 | ALICE FANTASIA | NULL || 15 | ALIEN CENTER | 71 || 15 | ALIEN CENTER | 72 || 15 | ALIEN CENTER | 73 || 15 | ALIEN CENTER | 74 || 15 | ALIEN CENTER | 75 || 15 | ALIEN CENTER | 76 | --------- ---------------- -------------- 11 rows in set (0.00 sec)

Ali Forever和Alien Center的结果相同,但是多出了Alice Fantasia这一新行,它的inventory.inventory_id列数据是空值null。此示例演示了外连接如何在不限制查询返回的行数的情况下是如何添加列值的。如果连接条件失败(如Alice Fantasia的情况),则从外连接表检索到的任何列都将为空值null。

1.1 左外连接和右外连接

上一节中,每个外连接示例里我都指定了left outer join。关键字left表示该连接左侧的表决定结果集中的行数,而右侧的表用于为找到的匹配项提供列值。但其实你也可以指定right outer join,在这种情况下,连接右侧的表负责确定结果集中的行数,而左侧的表用于为之提供列值。

以下是上一节中最后一个查询的变体,使用right outer join而不是left outer join:

mysql> SELECT f.film_id, f.title, i.inventory_id -> FROM inventory i -> RIGHT OUTER JOIN film f -> ON f.film_id = i.film_id -> WHERE f.film_id BETWEEN 13 AND 15; --------- ---------------- -------------- | film_id | title | inventory_id | --------- ---------------- -------------- | 13 | ALI FOREVER | 67 || 13 | ALI FOREVER | 68 || 13 | ALI FOREVER | 69 || 13 | ALI FOREVER | 70 || 14 | ALICE FANTASIA | NULL || 15 | ALIEN CENTER | 71 || 15 | ALIEN CENTER | 72 || 15 | ALIEN CENTER | 73 || 15 | ALIEN CENTER | 74 || 15 | ALIEN CENTER | 75 || 15 | ALIEN CENTER | 76 | --------- ---------------- -------------- 11 rows in set (0.00 sec)

请记住,上面两个版本的查询执行的都是外连接,而关键字left和right只是告诉服务器哪个表中的数据可以不足。因此如果你想将表A和B外连接,得到A的所有行数据和B中匹配列的额外数据,可以指定A left outer join B或B right outer join A。

注意

由于很少会遇到右外连接,而且并非所有数据库服务器都支持它们,因此我建议使用左外连接。outer关键字是可选的,因此你可以指定A left join B,但为了使代码更清晰,我建议包含outer关键字。

1.2 三路外连接

在某些情况下,你可能需要将一个表与另外两个表进行外连接。例如,可以拓展上一节中的查询,以包含来自rental表的数据:

mysql> SELECT f.film_id, f.title, i.inventory_id, r.rental_date -> FROM film f -> LEFT OUTER JOIN inventory i -> ON f.film_id = i.film_id -> LEFT OUTER JOIN rental r -> ON i.inventory_id = r.inventory_id -> WHERE f.film_id BETWEEN 13 AND 15; --------- ---------------- -------------- --------------------- | film_id | title | inventory_id | rental_date | --------- ---------------- -------------- --------------------- | 13 | ALI FOREVER | 67 | 2005-07-31 18:11:17 || 13 | ALI FOREVER | 67 | 2005-08-22 21:59:29 || 13 | ALI FOREVER | 68 | 2005-07-28 15:26:20 || 13 | ALI FOREVER | 68 | 2005-08-23 05:02:31 || 13 | ALI FOREVER | 69 | 2005-08-01 23:36:10 || 13 | ALI FOREVER | 69 | 2005-08-22 02:12:44 || 13 | ALI FOREVER | 70 | 2005-07-12 10:51:09 || 13 | ALI FOREVER | 70 | 2005-07-29 01:29:51 || 13 | ALI FOREVER | 70 | 2006-02-14 15:16:03 || 14 | ALICE FANTASIA | NULL | NULL || 15 | ALIEN CENTER | 71 | 2005-05-28 02:06:37 || 15 | ALIEN CENTER | 71 | 2005-06-17 16:40:03 || 15 | ALIEN CENTER | 71 | 2005-07-11 05:47:08 || 15 | ALIEN CENTER | 71 | 2005-08-02 13:58:55 || 15 | ALIEN CENTER | 71 | 2005-08-23 05:13:09 || 15 | ALIEN CENTER | 72 | 2005-05-27 22:49:27 || 15 | ALIEN CENTER | 72 | 2005-06-19 13:29:28 || 15 | ALIEN CENTER | 72 | 2005-07-07 23:05:53 || 15 | ALIEN CENTER | 72 | 2005-08-01 05:55:13 || 15 | ALIEN CENTER | 72 | 2005-08-20 15:11:48 || 15 | ALIEN CENTER | 73 | 2005-07-06 15:51:58 || 15 | ALIEN CENTER | 73 | 2005-07-30 14:48:24 || 15 | ALIEN CENTER | 73 | 2005-08-20 22:32:11 || 15 | ALIEN CENTER | 74 | 2005-07-27 00:15:18 || 15 | ALIEN CENTER | 74 | 2005-08-23 19:21:22 || 15 | ALIEN CENTER | 75 | 2005-07-09 02:58:41 || 15 | ALIEN CENTER | 75 | 2005-07-29 23:52:01 || 15 | ALIEN CENTER | 75 | 2005-08-18 21:55:01 || 15 | ALIEN CENTER | 76 | 2005-06-15 08:01:29 || 15 | ALIEN CENTER | 76 | 2005-07-07 18:31:50 || 15 | ALIEN CENTER | 76 | 2005-08-01 01:49:36 || 15 | ALIEN CENTER | 76 | 2005-08-17 07:26:47 | --------- ---------------- -------------- --------------------- 32 rows in set (0.01 sec)

结果包括库存中所有电影的租赁情况,但是电影Alice Fantasia对于两个外连接表的列都是空值null。

2. 交叉连接

我在第五章中介绍过笛卡尔积的概念,它本质上是不指定任何连接条件的情况下多表连接的结果。笛卡尔积在不经意的情况下使用得相当频繁(例如,忘记将连接条件添加到from子句中)xv’s’s’s’s’s’s’s’s’s’s’s’s’s’s’s’s’s’s,否则人们一般不会经常有意识地用到它。但是如果你的确要生成两个表的笛卡尔积,那么你应该指定交叉连接,如下所示:

mysql> SELECT c.name category_name, l.name language_name -> FROM category c -> CROSS JOIN language l; --------------- --------------- | category_name | language_name | --------------- --------------- | Action | English || Action | Italian || Action | Japanese || Action | Mandarin || Action | French || Action | German || Animation | English || Animation | Italian || Animation | Japanese || Animation | Mandarin || Animation | French || Animation | German |...| Sports | English || Sports | Italian || Sports | Japanese || Sports | Mandarin || Sports | French || Sports | German || Travel | English || Travel | Italian || Travel | Japanese || Travel | Mandarin || Travel | French || Travel | German | --------------- --------------- 96 rows in set (0.00 sec)

这个查询生成category和language表的笛卡尔积,得到96行(16个category行×6个language行)数据。你既然知道了交叉连接是什么,以及如何指定它,那么它是用来做什么的呢?大多数SQL书籍都会先描述交叉连接是什么,然后告诉你它很少有用,但是我也有发现交叉连接非常有用的情况。

第九章中,我讨论了如何使用子查询构造表。我使用的示例演示了如何构建一个可以连接到其他表的三行表。下面是示例中构造的表:

mysql> SELECT 'Small Fry' name, 0 low_limit, 74.99 high_limit -> UNION ALL -> SELECT 'Average Joes' name, 75 low_limit, 149.99 high_limit -> UNION ALL -> SELECT 'Heavy Hitters' name, 150 low_limit, 9999999.99 high_limit; --------------- ----------- ------------ | name | low_limit | high_limit | --------------- ----------- ------------ | Small Fry | 0 | 74.99 || Average Joes | 75 | 149.99 || Heavy Hitters | 150 | 9999999.99 | --------------- ----------- ------------ 3 rows in set (0.00 sec)

虽然这正是根据电影总付款将客户分为三组所需要的表,但如果你需要生成一个大表,那么使用集合操作符union all合并单行表的策略可能就不是很有用了。

例如,假设你希望创建一个查询,为2020年的每一天生成一行,但数据库中没有包含每天一行的表。使用第九章示例中的策略,可以执行以下操作:

SELECT '2020-01-01' dtUNION ALLSELECT '2020-01-02' dtUNION ALLSELECT '2020-01-03' dtUNION ALL.........SELECT '2020-12-29' dtUNION ALLSELECT '2020-12-30' dtUNION ALLSELECT '2020-12-31' dt

构建一个将366个查询的结果合并在一起的查询还是蛮无聊的,因此可能需要一个不同的策略。试想一下:首先通过单列生成366行的表,单列包含0到366中的某个数,然后将这个天数加上2020年1月1日,这样做又会如何呢?下面是一种可能生成这样一个表的方法:

mysql> SELECT ones.num tens.num hundreds.num -> FROM -> (SELECT 0 num UNION ALL -> SELECT 1 num UNION ALL -> SELECT 2 num UNION ALL -> SELECT 3 num UNION ALL -> SELECT 4 num UNION ALL -> SELECT 5 num UNION ALL -> SELECT 6 num UNION ALL -> SELECT 7 num UNION ALL -> SELECT 8 num UNION ALL -> SELECT 9 num) ones -> CROSS JOIN -> (SELECT 0 num UNION ALL -> SELECT 10 num UNION ALL -> SELECT 20 num UNION ALL -> SELECT 30 num UNION ALL -> SELECT 40 num UNION ALL -> SELECT 50 num UNION ALL -> SELECT 60 num UNION ALL -> SELECT 70 num UNION ALL -> SELECT 80 num UNION ALL -> SELECT 90 num) tens -> CROSS JOIN -> (SELECT 0 num UNION ALL -> SELECT 100 num UNION ALL -> SELECT 200 num UNION ALL -> SELECT 300 num) hundreds; ------------------------------------ | ones.num tens.num hundreds.num | ------------------------------------ | 0 || 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 10 || 11 || 12 |.........| 391 || 392 || 393 || 394 || 395 || 396 || 397 || 398 || 399 | ------------------------------------ 400 rows in set (0.00 sec)

如果生成三个集合{0、1、2、3、4、5、6、7、8、9}、{0、10、20、30、40、50、60、70、80、90}和{0、100、200、300}的笛卡尔积,并将三列相加,则得到一个400行的结果集,它包含0到399之间的所有数字。虽然这比要生成2020年天数的集合所需的366行数据要多,但其实去掉多余的行是很容易的,我随后会展示这一点。

下一步是将这组数字转换成一组日期。为此,我将使用date_add()函数将结果集中的每个数字加上2020年1月1日。然后我将添加一个过滤条件,以排除2021年的日期:

mysql> SELECT DATE_ADD('2020-01-01', -> INTERVAL (ones.num tens.num hundreds.num) DAY) dt -> FROM -> (SELECT 0 num UNION ALL -> SELECT 1 num UNION ALL -> SELECT 2 num UNION ALL -> SELECT 3 num UNION ALL -> SELECT 4 num UNION ALL -> SELECT 5 num UNION ALL -> SELECT 6 num UNION ALL -> SELECT 7 num UNION ALL -> SELECT 8 num UNION ALL -> SELECT 9 num) ones -> CROSS JOIN -> (SELECT 0 num UNION ALL -> SELECT 10 num UNION ALL -> SELECT 20 num UNION ALL -> SELECT 30 num UNION ALL -> SELECT 40 num UNION ALL -> SELECT 50 num UNION ALL -> SELECT 60 num UNION ALL -> SELECT 70 num UNION ALL -> SELECT 80 num UNION ALL -> SELECT 90 num) tens -> CROSS JOIN -> (SELECT 0 num UNION ALL -> SELECT 100 num UNION ALL -> SELECT 200 num UNION ALL -> SELECT 300 num) hundreds -> WHERE DATE_ADD('2020-01-01', -> INTERVAL (ones.num tens.num hundreds.num) DAY) < '2021-01-01' -> ORDER BY 1; ------------ | dt | ------------ | 2020-01-01 || 2020-01-02 || 2020-01-03 || 2020-01-04 || 2020-01-05 || 2020-01-06 || 2020-01-07 || 2020-01-08 |.........| 2020-02-26 || 2020-02-27 || 2020-02-28 || 2020-02-29 || 2020-03-01 || 2020-03-02 || 2020-03-03 |.........| 2020-12-24 || 2020-12-25 || 2020-12-26 || 2020-12-27 || 2020-12-28 || 2020-12-29 || 2020-12-30 || 2020-12-31 | ------------ 366 rows in set (0.03 sec)

这种方法的好处是在无需人工干预的情况下,结果集会自动包含额外的闰日(2月29日),因为数据库服务器基于2020年1月1日加上59天就可以计算出来。

现在你有了构造2020年所有天数的方法,那么要怎么使用它呢?你可能会被要求生成一份报告,显示2020年的每一天以及那一天的电影租赁数量。报告需要包括一年中的每一天,包括没有电影租赁操作的日子。查询可能是这样的(这里使用2005年来匹配rental表中的数据):

mysql> SELECT days.dt, COUNT(r.rental_id) num_rentals -> FROM rental r -> RIGHT OUTER JOIN -> (SELECT DATE_ADD('2005-01-01', -> INTERVAL (ones.num tens.num hundreds.num) DAY) dt -> FROM -> (SELECT 0 num UNION ALL -> SELECT 1 num UNION ALL -> SELECT 2 num UNION ALL -> SELECT 3 num UNION ALL -> SELECT 4 num UNION ALL -> SELECT 5 num UNION ALL -> SELECT 6 num UNION ALL -> SELECT 7 num UNION ALL -> SELECT 8 num UNION ALL -> SELECT 9 num) ones -> CROSS JOIN -> (SELECT 0 num UNION ALL -> SELECT 10 num UNION ALL -> SELECT 20 num UNION ALL -> SELECT 30 num UNION ALL -> SELECT 40 num UNION ALL -> SELECT 50 num UNION ALL -> SELECT 60 num UNION ALL -> SELECT 70 num UNION ALL -> SELECT 80 num UNION ALL -> SELECT 90 num) tens -> CROSS JOIN -> (SELECT 0 num UNION ALL -> SELECT 100 num UNION ALL -> SELECT 200 num UNION ALL -> SELECT 300 num) hundreds -> WHERE DATE_ADD('2005-01-01', -> INTERVAL (ones.num tens.num hundreds.num) DAY) -> < '2006-01-01' -> ) days -> ON days.dt = date(r.rental_date) -> GROUP BY days.dt -> ORDER BY 1; ------------ ------------- | dt | num_rentals | ------------ ------------- | 2005-01-01 | 0 || 2005-01-02 | 0 || 2005-01-03 | 0 || 2005-01-04 | 0 |...| 2005-05-23 | 0 || 2005-05-24 | 8 || 2005-05-25 | 137 || 2005-05-26 | 174 || 2005-05-27 | 166 || 2005-05-28 | 196 || 2005-05-29 | 154 || 2005-05-30 | 158 || 2005-05-31 | 163 || 2005-06-01 | 0 |...| 2005-06-13 | 0 || 2005-06-14 | 16 || 2005-06-15 | 348 || 2005-06-16 | 324 || 2005-06-17 | 325 || 2005-06-18 | 344 || 2005-06-19 | 348 || 2005-06-20 | 331 || 2005-06-21 | 275 || 2005-06-22 | 0 |...| 2005-12-27 | 0 || 2005-12-28 | 0 || 2005-12-29 | 0 || 2005-12-30 | 0 || 2005-12-31 | 0 | ------------ ------------- 365 rows in set (8.99 sec)

到目前为止,这是本书中最有趣的查询之一,因为它包括交叉连接、外连接、日期函数、分组、集合操作(union all)和聚合函数(count())。虽然对于给定的问题,它不一定是最优雅的解决方案,但它作为例子至少能够给我们一些启迪:只要牢牢掌握该语言并有一点创造力,即使是很少使用的特性(如交叉连接)也可以在SQL工具箱中成为一个有效的工具。

3. 自然连接

假如你想少动点脑子,可以选择这样的连接类型:只命名需要连接的表,连接条件的具体类型由数据库服务器决定。这种连接类型称为自然连接(natural join),它依赖多表交叉时的相同列名来推断正确的连接条件。例如,rental表包含一个名为customer_id的列,该列是customer表的外键,其主键也是customer_id。因此,你可以尝试使用自然连接编写查询来连接这两个表:

mysql> SELECT c.first_name, c.last_name, date(r.rental_date) -> FROM customer c -> NATURAL JOIN rental r;Empty set (0.04 sec)

因为你指定了自然连接,所以服务器检查了表定义并添加了连接条件r.custome_id=c.customer_id来连接这两个表。这本可以很好地工作,但在Sakila模式(本书使用的示例数据库)中,所有表都包含last_update列,以显示每一行执行最新修改的时间,因此服务器还添加了连接条件r.last_update = c.last_update,这会导致查询不返回任何数据。

解决此问题的唯一方法是使用子查询来限制至少一个表的列:

mysql> SELECT cust.first_name, cust.last_name, date(r.rental_date) -> FROM -> (SELECT customer_id, first_name, last_name -> FROM customer -> ) cust -> NATURAL JOIN rental r; ------------ ----------- --------------------- | first_name | last_name | date(r.rental_date) | ------------ ----------- --------------------- | MARY | SMITH | 2005-05-25 | | MARY | SMITH | 2005-05-28 | | MARY | SMITH | 2005-06-15 | | MARY | SMITH | 2005-06-15 | | MARY | SMITH | 2005-06-15 | | MARY | SMITH | 2005-06-16 | | MARY | SMITH | 2005-06-18 | | MARY | SMITH | 2005-06-18 | ... | AUSTIN | CINTRON | 2005-08-21 | | AUSTIN | CINTRON | 2005-08-21 | | AUSTIN | CINTRON | 2005-08-21 | | AUSTIN | CINTRON | 2005-08-23 | | AUSTIN | CINTRON | 2005-08-23 | | AUSTIN | CINTRON | 2005-08-23 | ------------ ----------- --------------------- 16044 rows in set (0.03 sec)

现在想想,为了省事而不指定连接条件,反而产生了这种麻烦,到底值不值得?——绝对是不值得的!所以我们应该避免使用这种连接类型,而是使用有明确连接条件的内连接。

十点英语:老人与海里不断下沉的鱼线,老人如何与鱼抗争的呢?

核心词汇

1. virtue /ˈvɜːrtʃuː/ (n.) 美德

2. annoy /əˈnɔɪ/ (v.) 使烦恼,打搅

3. feed /fiːd/ (v.) 喂食

4. surface /ˈsɜːrfɪs/ (n.) 表面

5. annul /əˈnʌl/ (v.) 宣布无效

6. distinguish /dɪˈstɪŋɡwɪʃ/ (v.) 区分

7. tentative /ˈtentətɪv/ (adj.) 试探性的

8. unleash /ʌnˈliːʃ/ (v.) 释放

9. delicate /ˈdelɪkət/ (adj.) 轻微的;娇弱的

10. gentle /ˈdʒentl/ (adj.) 温柔的

11. imperceptible /ˌɪmpərˈseptəbl/ (adj.) 无法察觉到的

核心短语

1. think of 想起

2. pick up 捡起

3. trickle down 一滴滴向下流

4. trade for 以…换得,贸易交换

5. reach out for 伸手去拿

6. run through 连续贯穿

7. break from 脱离,挣脱

8. slip down 滑落

9. move off 离开

选段正文

He did not remember when he had first started to talk aloud when he was by himself.

He had sung when he was by himself in the old days and he had sung at night sometimes when he was alone steering(驾驶)on his watch in the smacks or in the turtle boats.

He had probably started to talk aloud, when alone, when the boy had left. But he did not remember. When he and the boy fished together they usually spoke only when it was necessary.

They talked at night or when they were storm-bound(被暴风所困的)by bad weather. It was considered a virtue(美德)not to talk unnecessarily at sea and the old man had always considered it so and respected it.

But now he said his thoughts aloud many times since there was no one that they could annoy(干扰).

"If the others heard me talking out loud they would think that I am crazy," he said aloud. "But since I am not crazy, I do not care.

And the rich have radios to talk to them in their boats and to bring them the baseball." Now is no time to think of baseball, he thought. Now is the time to think of only one thing.

That which I was born for. There might be a big one around that school, he thought. I picked up only a straggler(流浪的动物)from the albacore(青花鱼) that were feeding(进食,捕食)

But they are working far out and fast. Everything that shows on the surface(表面)today travels very fast and to the northeast.

Can that be the time of day? Or is it some sign of weather that I do not know?

He could not see the green of the shore now but only the tops of the blue hills that showed white as though they were snowcapped(被白雪覆盖着的)and the clouds that looked like high snow mountains above them.

The sea was very dark and the light made prisms(棱镜)in the water.

The myriad(无数的)flecks(斑点,微粒)of the plankton were annulled(取消,宣告无效)now by the high sun and it was only the great deep prisms in the blue water that the old man saw now with his lines going straight down into the water that was a mile deep.

The tuna, the fishermen called all the fish of that species tuna and only distinguished(被区分开的)among them by their proper names when they came to sell them or to trade them for baits, were down again.

The sun was hot now and the old man felt it on the back of his neck and felt the sweat trickle down his back as he rowed.

I could just drift(漂流), he thought, and sleep and put a bight of line around my toe to wake me. But today is eighty-five days and I should fish the day well.

Just then, watching his lines, he saw one of the projecting green sticks dip(下沉) sharply.

"Yes," he said. "Yes," and shipped his oars without bumping the boat. He reached out for the line and held it softly between the thumb(大拇指) and forefinger(食指)of his right hand. He felt no strain(张力) nor weight(重量) and he held the line lightly.

Then it came again. This time it was a tentative(试探性的) pull, not solid nor heavy, and he knew exactly what it was.

One hundred fathoms down a marlin(马林鱼)was eating the sardines(沙丁鱼) that covered the point and the shank(柄)of the hook where the hand-forged(手工锻造的)hook projected from the head of the small tuna.

The old man held the line delicately, and softly, with his left hand, unleashed(释放,解放)it from the stick. Now he could let it run through his fingers without the fish feeling any tension.

This far out, he must be huge in this month, he thought. Eat them, fish. Eat them. Please eat them. How fresh they are and you down there six hundred feet in that cold water in the dark.

Make another turn in the dark and come back and eat them. He felt the light delicate(轻微的)pulling and then a harder pull when a sardine's head must have been more difficult to break from the hook. Then there was nothing.

“Come on," the old man said aloud. "Make another turn. Just smell them. Aren't they lovely? Eat them good now and then there is the tuna. Hard and cold and lovely. Don't be shy, fish. Eat them.”

He waited with the line between his thumb and his finger, watching it and the other lines at the same time for the fish might have swum up or down. Then came the same delicate pulling touch again.

"He'll take it," the old man said aloud. “God help him to take it.” He did not take it though. He was gone and the old man felt nothing.

“He can’t have gone,” he said. “Christ knows he can't have gone. He's making a turn. Maybe he has been hooked before and he remembers something of it.”

Then he felt the gentle(温柔的)touch on the line and he was happy.

“It was only his turn,” he said. “He’ll take it.” He was happy feeling the gentle pulling and then he felt something hard and unbelievably(无法相信得)heavy.

It was the weight of the fish and he let the line slip down, down, down, unrolling off the first of the two reserve coils(鱼线卷).

As it went down, slipping lightly through the old man's fingers, he still could feel the great weight, though the pressure of his thumb and finger were almost imperceptible(感觉不到的,极细微的).

“What a fish,” he said. “He has it sideways in his mouth now and he is moving off with it.”

Then he will turn and swallow(吞)it, he thought. He did not say that because he knew that if you said a good thing it might not happen.

He knew what a huge fish this was and he thought of him moving away in the darkness with the tuna held crosswise(横向交叉,成十字的)in his mouth.

At that moment he felt him stop moving but the weight was still there. Then the weight increased and he gave more line. He tightened(加固,拉紧) the pressure of his thumb and finger for a moment and the weight increased and was going straight down.

“He’s taken it,” he said. “Now I’ll let him eat it well.”

免责声明:本文由用户上传,如有侵权请联系删除!