验证码与人工智能的激荡二十年:全面瓦解

上一篇我们介绍了字符验证码在诞生之初面对图像识别算法时的情况,使用这种算法识别字符验证码的方式鲁棒性比较差,有可能验证码添加一种新的干扰或者底噪,识别算法就要进行大幅调整和更新,而且每次更新都需要手工分析。采用这种方式的攻击者在对抗中很难占到优势。

随着 AI 社区 CV (Computer Vision)方向的发展,CNN 在很多视觉相关的任务上的表现都取得了显著的进步,同时也在无形中扮演了字符验证码的「掘墓人」的角色。当下 CNN 的各种科研论文、开源项目、科普文章俯拾皆是,本文我们会把重点放在字符验证码的安全性是如何被 CNN 模型逐渐瓦解的。

一、CNN

卷积神经网络(Convolutional neural network),一种广泛应用于图像和视频识别、图像分类、自然语言处理等任务的神经网络。现在普遍认为生物学上关于哺乳动物大脑工作原理的研究对 CNN 的起源产生很大影响,其中最著名的是 Hubel 和 Wiesel 关于猫的视觉实验,这个成果也为他们赢得了 1981 年的诺贝尔奖。

早在 1980 年,日本学者 K Fukushima 就设计了一个 neocognitron 模型,现代大部分常用的 CNN 结构都已经在这个模型上得以体现[1]。之后 LeCun 先是把反向传播(Back Propagation)引入到类 neocognitron 模型的训练中,又在 1998 年提出了著名的 LeNet-5,并在 MNIST 识别任务上技压群雄[4]。但是后来 CNN 反而沉寂了 10 年的时间,直到 2012 年 ILSVRC(ImageNet Large Scale Visual Recognition Challenge)竞赛上,AlexNet 以仅 15.3% 错误率的绝对优势登顶,AI 社区又重新掀起了 CNN 等深度神经网络的研究热潮,仅每年 ILSVRC 竞赛的冠亚军,就诞生出 ResNet、GoogLeNet、VGG 这些早已写入教科书的模型结构。

左边是 neocognitron 内部层和层之间相互关联的示意图[1],右边是 LeNet-5 的结构图[4],可以看出两者之间的相似,或者说传承与发展

CNN 的关键结构包括用来提取空间特征的卷积层(Convolutional layer)以及用于减小特征尺寸的下采样(Down-sampling)层等。

1.1 卷积层(Convolutional Layer)

卷积是一种数学运算,连续定义为

其中 f 和 g 都是在 R 上的可测函数,卷积操作就是把其中一个函数翻转之后与另一个函数的乘积的积分。

离散定义为

可以看做离散点上两个函数(其中一个翻转过)值的加权求和。在二维离散函数上就成了常见的CNN 卷积层计算原理:

图片来自stackexchange

严格按照卷积的定义,进行加权求和之前应该把函数 f 或者 g(这里分别是输入的矩阵和 filter)其中一个进行转置,但是实际 CNN 的卷积操作实现中没有这一步,因此卷积神经网络中进行的「卷积」运算绝大多数都是用「相关」(correlation)运算实现的,不过这并不影响什么。

1.2 采样层(Pooling Layer)

用于快速减小 feature map 的尺寸,进而减少较深的 NN 中参数的数量以及降低计算消耗。一般最常用的是 max pooling 操作,就是取目标区域各自的最大值作为本层的输出:

图片来自维基百科

一些 CNN 结构中也会使用 stride > 1 的 Conv Layer 去取代 max pooling,比如 GoogLeNet[3]。

二、CNN versus CAPTCHA

上一期我们介绍了早在 2003 年就有研究人员利用图像识别算法对字符验证码进行识别并得到一个比较不错的识别效果。而 CNN 这个早就实际应用于银行支票等图像识别场景的利器,自然也会被放到验证码的角斗场中。而在这两者之间的对抗中,字符验证码几乎从一开始就处于被碾压的状态。

2.1 CAPTCHA交锋CNN

Kumar Chellapilla 等在 2004 年就尝试使用基于[5]的 CNN 结构对当时实际使用中的几款字符验证码(包括我们的老朋友 EZ-Gimpy)进行识别,而且是不借助语言模型(字符验证码词库总量有限的特性)的实验,分别在 Mailblocks、Register、EZ-Gimpy、Ticketmaster、Yahoo version 2 和 GMail 这几款当时业界常用验证码数据集上达到 66.2%、47.8%、34.4%、4.9%、45.7% 以及 4.89% 的准确率[6]。

紧接着,Kumar Chellapilla 又在 2005 年利用 7 组对比实验,对人类和机器在指定条件(大写字母 A-Z 和数字 0-9、字体大小为 30、Times New Roman 字体等)下完成单个字符识别任务的能力进行了比对[7]。每组实验分别在字符图片中加入类型不同且程度递增的扭曲或干扰,人类组招募了 44 个视力(或者矫正视力)正常、年龄在 21-58 岁的同一家公司的职员在线完成实验;机器组采用基于[5]的 CNN 结构(在单独的训练集上分别训练后),用识别正确率作为对比指标。

7 组对比实验中,只有少数几组的结果上人类和机器的表现基本持平;其他实验中,随着干扰的升级,人类的准确率严重下降,而机器的准确率基本不受影响或影响较小。因此作者得出结论,“组成HIP(验证码的另一个名字,Human Interaction Proofs)的识别任务对机器更容易,对人更难”,同时建议字符验证码的安全强度应该更多依赖于字符分割(segmentation)的难度而不是字符识别的难度。实际上,这也是后来绝大多数主流字符验证码在设计上遵循的重要原则。

KC 对比实验中的第 4 组,左边是干扰难度递增的字符图片示例,右边是在不同难度下机器和人类参与者的识别准确率曲线对比,可以看出机器的识别效果基本一直都优于人类参与者。

2.2 CAPTCHA的无效抵抗

05 年之后,KC 的实验结论得到学术、工业界的广泛认同,越来越多的验证码都在让字符难以被确定位置(segmentation resistance)这一方向上努力。比如 Microsoft CAPTCHA、Hotmail、reCAPTCHA 等。

微软的 MSN 验证码[14]

在这个趋势下,这些针对字符验证码的识别实验也基本都按照以下两个步骤的思路进行:

- segmentation 分离出单个字符

- recognition 识别单个字符

其中 segmentation 阶段的技巧非常多样,包括填色(color filling)、分析字符宽度(character width)和特征(character feature)等;而分离出的单个字符,就可以用 CNN 达到 95% 的识别率[7]。按照这个思路,J. Yan 及其团队从 2008 到 2013 年分别在Microsoft CAPTCHA、Megaupload、reCAPTCHA、Yahoo等数据集上达到 61%、78%、33%、36%-89% 的准确率[14-17]。

Megauplaod 验证码,在 segmentation resistance mechanism 的路上走得格外远的一位参赛选手 可以看到在这个时期,CNN 更多的是「打辅助」的角色。

2.3 最强CAPTCHA的落幕

2013 年的时候,Google 团队就宣布了 reCAPTCHA 的一个重要更新:基于高级的风险分析,reCAPTCHA 可以分别给人类用户和机器推送不同难度的字符图片[8]。一年之后他们的另一篇博客[9]再次强调了这一点,并引用了 Google 的一篇论文里的实验结果[10]来解释这样做的原因:

Goodfellow 的团队在研究利用深层卷积神经网络解决街景标牌上字符识别的问题时,设计了一个把定位、分割和识别三个步骤合为一体的端到端(End to end)模型,在 SVHN(Street View House Numbers) 数据集上达到 96%的准确率。

之后为了进一步测试模型在识别任意人工合成的干扰字符图像任务上的泛化效果,他们在 reCAPTCHA 字符验证码上进行实验,使用的模型深度甚至比在 SVHN 上的更浅(9 个卷积层,相比于 SVHN 数据集上实验效果最好的 11 个卷积层),而数据集是由 reCAPTCHA 题目中最难的那些图片组成的(论文没有具体介绍对于「最难」题目的选择标准,推测可能是之前的识别实验准确率最低,或是在实际生产中用户答错率最高的那部分题目图片)。

在这样的前提下,模型对图片文本的成功转写(transcribing)率(对图片字符的识别准确率)为 99.8%,远远优于人类在这个任务上的表现(基于以上对于数据集选取标准的推测)。因此无论是论文[10]还是 reCAPTCHA 团队的博文,都会强调 reCAPTCHA 已经不把用户输入的字符是否正确作为唯一区分人机的标准,而是采用分析用户交互行为等更高级的手段。

2018 年的 3 月 31 号,字符验证码 reCAPTCHA V1 终止了服务,现在大家熟知的是要求用户选择符合要求的图片的 V2 版本以及没有界面的 V3 版本。

三、Try it yourself

本次实验随机生成 30000 张图片,有 29726 个标签(目录),因为有的标签生成了 2 张或者多张图片。以如下方式组织数据集:

CAPTCHA 库默认配置下生成的字符验证码风格如下:

可以看到里面包含了字符验证码常见的字符扭曲、重叠和点、线的干扰。

step2:加载数据集(原仓库作者自己写了一个 generator,这里为了简便,用 tensorflow.data.Dataset 等 API 实现):

def make_dataset(path, batch_size, n_label):
    def parse_image(filename):
        image = tf.io.read_file(filename)
        image = tf.image.decode_png(image, channels=3)
        image = tf.image.resize(image, [H, W])
        image = tf.image.per_image_standardization(image)
        return image

    def configure_for_performance(ds):
        ds = ds.shuffle(buffer_size=1000)
        ds = ds.batch(batch_size)
        ds = ds.repeat()
        ds = ds.prefetch(buffer_size=tf.data.experimental.AUTOTUNE)
        return ds

    filenames = glob(path + '/*/*')
    random.shuffle(filenames)
    labels = [[tf.keras.utils.to_categorical(((ord(i)-48)%39), n_label) for i in filename.split("/")[-2]] for filename in filenames]
    filenames_ds = tf.data.Dataset.from_tensor_slices(filenames)
    images_ds = filenames_ds.map(parse_image, num_parallel_calls=tf.data.experimental.AUTOTUNE)
    labels_ds = tf.data.Dataset.from_tensor_slices(labels)
    ds = tf.data.Dataset.zip((images_ds, labels_ds))
    ds = configure_for_performance(ds)

    return ds

篇幅所限,其余部分代码可以参考 tensorflow.data.Dataset 文档等资料。

step3:模型结构代码。模型只包括 3 个 2 维卷积层、3 个下采样层和两个全连接层,3.4M 个参数。相比于当下常用的 CNN 模型动辄上百层、参数上千万,这个结构是比较简洁和原始的。

input_layer = Input(shape=(H, W, C))
x = layers.Conv2D(32, 3, activation='relu')(input_layer)
x = layers.MaxPooling2D((2, 2))(x)
x = layers.Conv2D(64, 3, activation='relu')(x)
x = layers.MaxPooling2D((2, 2))(x)
x = layers.Conv2D(64, 3, activation='relu')(x)
x = layers.MaxPooling2D((2, 2))(x)

x = layers.Flatten()(x)
x = layers.Dense(1024, activation='relu')(x)

x = layers.Dense(D * N_LABELS, activation='softmax')(x)
x = layers.Reshape((D, N_LABELS))(x)

model = models.Model(inputs=input_layer, outputs=x)

model.compile(optimizer='adam',
              loss='categorical_crossentropy',
              metrics= ['accuracy'])

step4:训练模型:

ds = make_dataset("captcha_30000", batch_size, N_LABELS)
val_ds = make_dataset("captcha_1000", batch_size, N_LABELS)
history = model.fit(ds,
                    steps_per_epoch=30000//batch_size,
                    epochs=10,
                    validation_data=val_ds,
                    validation_steps=1000//batch_size
                   )

训练的 batch_size 取 128,验证数据集使用单独生成的 1000 个样本,训练 10 个 epoch:

可以看到在第 5 个 epoch 左右的时候就已经收敛到接近 80% 的 Validation 准确率。

step5:验证一下实验结果

import numpy as np
from matplotlib import pyplot
generator = ImageCaptcha(width=100, height=60)
chars_dict = "0123456789abcdefghijklmnopqrstuvwxyz"
fig, axes = plt.subplots(3, 3, figsize=(10, 10))

for i in range(9):
    label = "".join(random.choices(chars_dict, k=4))

    img = generator.generate_image(label)
    image = tf.keras.preprocessing.image.img_to_array(img)
    image = tf.image.per_image_standardization(image)

    pred = model.predict(tf.expand_dims(image, axis=0))
    pred = np.argmax(pred,axis=-1)
    pred = "".join([chr(i+ord('0')) if i<10 else chr(i+ord('a')-10) for i in pred[0]])
    ax = axes.flat[i]
    ax.imshow(img)
    ax.set_title('pred: {}'.format(pred))
    ax.set_xlabel('true: {}'.format(label))
    ax.set_xticks([])
    ax.set_yticks([])

训练过程仅仅耗时 11 min(8C/32G/GTX1070),整个实验的核心代码(构建模型、加载数据、训练)不到 50 行(虽然用代码行数证明观点有些诡辩的味道)。

这里使用一个极简单的 CNN 结构(只有三个 Conv Layer,LeNet-5 都有两个),在 30K 数据上很快达到 80% 左右的准确率(Validation Accuracy),而通过多次试验以及仓库原作者的实验结果(原仓库代码的实验结果是针对 4 位数字字符验证码的,分别在 30241 和 302410 个样本的情况下达到 87.6% 和 98.8% 的准确率)可以发现模型的效果很大程度上依赖于数据集的数量,在一定范围内,训练可用的数据越多,模型效果越好;同时还可以发现随着数据量的增大,准确率的提升越来越慢,也就是到训练后期为了提高几个百分点的准确率,可能要增加近一倍的数据量。

虽然今天有各种各样的途径可以获取验证码图片这种常见的数据集,但是作为这个任务几乎仅剩的门槛,还是有很多成熟的方法可以应对数据量的问题,比如最常用的数据增广(Data Augmentation),也就是对现有的图片数据进行随机翻转,缩放,剪裁,调整对比度、饱和度等 CV 操作,从而达到增多训练数据集的效果。这里对原始实验代码进行简单修改:

def make_dataset(path, batch_size, n_label, is_training=False):
    def parse_image(filename):
        image = tf.io.read_file(filename)
        image = tf.image.decode_png(image, channels=3)
        image = tf.image.resize(image, [H, W])
        image = tf.image.per_image_standardization(image)
        if is_training:
            image = tf.image.resize_with_crop_or_pad(image, H+10, W+10)
            image = tf.image.random_crop(image, (H, W, 3))
            image = tf.image.random_brightness(image, 0.3)
        return image

        ...

model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
                  loss='categorical_crossentropy',
                  metrics= ['accuracy'])
        ...

ds = make_dataset("captcha_10000", batch_size, N_LABELS, True)
val_ds = make_dataset("captcha_1000", batch_size, N_LABELS)
history = model.fit(ds,
                    steps_per_epoch = 10000//batch_size,
                    epochs = 50,
                    validation_data = val_ds,
                    validation_steps = 1000//batch_size
                   )

只是对原训练数据集的样本进行了一个缩放剪裁和亮度的调整,作为示例。只使用 10K 图片,训练 50 个 epoch(这里调大 Adam 的学习率),可以在 28 个 epoch 左右达到接近 80% 的 ValAcc。图片增广方式的选取要根据实际需求,比如这个任务中随机翻转,不管是竖向还是横向,都不是一个好的选择。

加入两种简单的增广并调大 learning rate 之后,模型在只有原先数据集三分之一的数据上得到同样接近 80% 的验证准确率 。

通过图片增广,可以进一步降低字符验证码识别任务对数据集数量的要求,用有限的数据取得更好的效果。此外 keras.applications 模块 还提供很多成熟的 CNN 模型以及这些模型在 imagenet 上的学习结果,也就意味着如果采用迁移学习的方式,整个实验过程还可以进一步简化。

除[11]之外,GitHub 上还有很多结构更复杂、功能更强大,同时又被封装得更易于使用的开源项目,大多数都是这样的端到端模型,有的项目甚至还提供了有图形界面的 Win32 可执行程序,基本做到了开箱即用,把字符验证码的识别任务难度降低到几乎只需要考虑如何获取样本的程度。而在各种**平台泛滥的当下,这类样本数据的获取其实也没有很高的难度了。

四、Conclusion

综上所述,传统字符验证码在强大的CNN技术下已经毫无安全性可言,而且随着人工智能技术的普及,攻击的成本与门槛变得极为低下,因此可以说传统字符验证码的安全体系已经被全面瓦解。与这种局面恰好同时发生的是,互联网已经成为现代社会经济活动的最重要平台,线上资产呈现几何级数的增加。验证码作为保护线上资产的第一道关口,有着无可比拟的重要性,它的安全阵线的全面瓦解给各行各业企业的线上资产造成了巨大的风险,而且损失的金额逐年增加到数千亿的级别。

迫于强大的安全压力,传统字符验证码走进了死胡同,各种为了安全性提升的变形扭曲等手段不仅没有丝毫挽回一点局面,反而让用户的使用体验降低到难以忍受的境地。或许是由于 CAPTCHA 字符验证码的概念太深入人心,或许是面对强大的人工智能设计一款绝对通用的验证码真的很困难,全球范围内面临这个痛点多年来依然束手无策,直到一个来自中国武汉的极客团队创造性的提出了一个划时代的验证理念。他们看到人工智能的发展大趋势之下,必须非常创造性的真正利用人的生物行为特征,构建一个以人工智能为内核的验证码,才能做到持久智能的安全,而以这种理念打造的验证码被称为行为式验证。

接下来,请看下篇:「验证码与人工智能的激荡二十年:行为革命」

[1] Fukushima, K. . "Neocognitron: A self-organizing neural network model for a mechanism of pattern recognition unaffected by shift in position." Biological Cybernetics 36.4(1980):193-202.

[2] Wikipedia - Convolutional neural network

[3] Szegedy, C. , et al. "Going deeper with convolutions." 2015 IEEE Conference on Computer Vision and Pattern Recognition (CVPR) IEEE, 2015.

[4] Lecun, Y. , and L. Bottou . "Gradient-based learning applied to document recognition." Proceedings of the IEEE 86.11(1998):2278-2324.

[5] Simard, Patrice Yvon , D. Steinkraus , and J. C. Platt . "Best Practices for Convolutional Neural Networks Applied to Visual Document Analysis." 7th International Conference on Document Analysis and Recognition (ICDAR 2003), 2-Volume Set, 3-6 August 2003, Edinburgh, Scotland, UK IEEE Computer Society, 2003.

[6] Chellapilla, K. , and P. Y. Simard . "Using Machine Learning to Break Visual Human Interaction Proofs (HIPs)." DBLP DBLP, 2004:265--272.

[7] Larson, K. , et al. "Computers beat Humans at Single Character Recognition in Reading based Human Interaction Proofs (HIPs). " (2005).

[8] Google Security Blog, reCAPTCHA just got easier (but only if you’re human) by Vinay Shet, Product Manager, reCAPTCHA

[9] Google Security Blog, Street View and reCAPTCHA technology just got smarter by Vinay Shet, Product Manager, reCAPTCHA

[10] Goodfellow, I. J. , et al. "Multi-digit Number Recognition from Street View Imagery using Deep Convolutional Neural Networks." Computer Science (2013).

[11] GitHub - JackonYang/captcha-tensorflow

[12] GitHub - lepture/captcha

[13] pypi - captcha

[14] J. Yan and A. S. E. Ahmad, “A low-cost attack on a microsoft CAPTCHA,” in Proceedings of the 15th ACM conference on Computer and Communications Security, CCS’08, pp. 543–554, USA, October 2008.

[15] A. S. El Ahmad, J. Yan, and L. Marshall, “The robustness of a new CAPTCHA,” in Proceedings of the 3rd European Workshop on System Security, EUROSEC’10, pp. 36–41, April 2010.

[16] A. S. E. Ahmad, J. Yan, and M. Tayara, “The Robustness of Google CAPTCHAs,” Computing Science Technical Report CS- TR-1278, Newcastle University, 2011.

[17] H. Gao, W. Wang, J. Qi, X. Wang, X. Liu, and J. Yan, “The robustness of hollow CAPTCHAs,” in Proceedings of the ACM SIGSAC Conference on Computer and Communications Security, CCS 2013, pp. 1075–1085, November 2013.

(0)

相关推荐

  • 手把手教你解决90%的NLP问题

    作者:Emmanuel Ameisen 编译:ronghuaiyang 导读 利用机器学习方法来理解和利用文本,从最简单的到state-of-the-art,由浅入深,循序渐进. 文本数据到处都是 无 ...

  • TeachAI 实验思考

    很早之前就想做一款训练模型的工具,最初的灵感是来自谷歌实验室尝试教机器学习手势用来玩游戏- 近些年来,艺术模型工具有runway.ai,微软最近出了lobe-- Lobe通过免费,易于使用的应用程序帮 ...

  • 人人都恨的验证码,想不到背后的故事这么有意思

    现在上网冲浪,当你完成注册登录或者做一些操作的时候,随之而来的会是各式各样的验证码. 虽然我们不怵任何验证,但总有被验证码气的抓耳挠腮的时候. 尤其是早先的字符验证码,不经意间就会被那些随机出现的字符 ...

  • 用户画像方法小结

    用户画像,英文为Customer Profiling或Persona,核心在于标签化,目的在于得出结论,业务决策. 一.实践经验 管理层想要用户画像,个人感觉主要有两方面的业务原因: 1.风控,特别是 ...

  • TF之CNN:基于CIFAR-10数据集训练、检测CNN(2+2)模型(TensorBoard可视化)

    TF之CNN:基于CIFAR-10数据集训练.检测CNN(2+2)模型(TensorBoard可视化) 1.基于CIFAR-10数据集训练CNN(2+2)模型代码 from datetime impo ...

  • 《十年》歌词怎么重配?-试试SongNet!

    论文:Rigid Formats Controlled Text Generation 作者:Piji Li.Haisong Zhang.Xiaojiang Liu.Shuming Shi 机构:Te ...

  • 神经网络中的蒸馏技术,从Softmax开始说起

    作者:Sayak Paul 编译:ronghuaiyang 导读 从各个层次给大家讲解模型的知识蒸馏的相关内容,并通过实际的代码给大家进行演示. 本报告讨论了非常厉害模型优化技术 -- 知识蒸馏,并给 ...

  • 基于一种改进的Wide&Deep 文本分类在用户身份识别上的实践

    导读 在互联网平台中,通常包含C端和B端两种客户.为了更好地服务C端用户,识别出用户身份十分重要.本文详细介绍了在用户身份识别过程中如何解决冷启动时少样本的问题,并比较了关键词匹配.XGBoost.T ...

  • 激荡二十年 —— 手机相机技术的发展历程

    本站教学视频<成像系统图像质量调试(新上线)><成像算法基础(python版)> <成像系统镜头光学><新版图像质量测试测量与国际标准><新版cm ...

  • 西红门,激荡二十年!

    今年拍出的地块,入市速度明显加快. 两个月前,四环外地段优质的西红门两块地被拍出,引起市场惊叹:限竞房奔7万/㎡,标志大兴也要进入豪宅时代! 其实,北京楼市不限价的口子已经打开,今年多块土地竞拍激烈, ...

  • 激荡二十年,何处是我家?

    在很多名人的传记文章或电视采访中,经常能看到听到这样的片段: "我拖着疲惫的身体和行李随着人流走出北京站,站口的大街上跑着黄色面的,即使是十块钱也是坐不起的,然后径直走上了长安街.天色渐暗, ...

  • RNAi药物的激荡二十年,希望就在眼前?

    自从20年前首次发现RNAi(RNA interference, RNA干扰)以来,无论是学术机构还是制药公司的科研人员一直希望将这种技术开发成能够治疗疾病的新疗法,但到目前为止仍然没有任何一款相关药 ...

  • 激荡四百年:司马衷含泪离开洛阳,苦守两年皇甫重一朝瓦解

    本文是长篇系列文章<激荡四百年>的第32篇 李雄和刘渊公然自立,朝廷不可能不管,征讨的任务分别落在了罗尚和司马腾头上. 作为益州刺史,罗尚想要在晋朝站稳脚跟,除了击败李雄,他别无选择. 永 ...

  • 激荡四百年:皇甫重坚守两年一朝瓦解,张轨平定叛乱立威凉州

    本文是长篇系列文章<激荡四百年>的第36篇 前面说到,李含想要谋害皇甫重,皇甫重先下手为强起兵讨伐他,司马乂不想事情闹大,一面征召李含入朝,一面令皇甫重停战. 然而,皇甫重和李含矛盾已深, ...

  • 短信验证码须知

    短信验证码现在应用非常广泛,很多地方都需要用到,如网站注册,账号登录.找回密码.修改密码.网上支付等,但是有时候会出现获取短信验证码收不到的情况,非常令人头疼,那么导致短信验证码收不到的原因有哪些呢? ...

  • 厨师长教你二十年秘制煲仔饭“酱汁”的专业做法,方法简单实用

    厨师长教你二十年秘制煲仔饭“酱汁”的专业做法,方法简单实用

  • 浅析人工智能的发展方向

    众所周知人工智能现如今正在高速发展,并且深入人们的生活和工作中,这不仅对人工的生活和工作提供了便利,同时也对人们未来的生活产生了影响.那么未来人工智能的发展方向主要在哪些方面? 一是在治疗方面,开发出 ...