2018年5月3日星期四

如何使用DeepFake实现视频换脸

不久之前,AV 视频换脸明星的 DeepFake 火了。这篇文章将一步步教你如何实现换脸。


如果你是第一次听说 DeepFake,一定要点击上面的视频,亲自感受一下尼古拉斯的脸是如何占据全世界的每一个影片。

项目实战

我们要如何实现视频里的变脸呢?

因为视频是连续的图片,那么我们只需要把每一张图片中的脸切换了,就能得到变脸的新视频了。那么如何切换一个视频中的图片呢? 这需要我们 首先找到视频中的脸,然后把脸进行切换。我们会发现,变脸这个难题可以拆解成如下的流程。

于是,在我们会在后续按照这五个步骤进行介绍。

视频转图像

FFmpeg

FFmpeg 提供了处理音频、视频、字幕和相关源数据的工具库。核心的库包括:

  • libavcodec 提供了处理编码的能力

  • libavformat 实现了流协议、容器类型、基本的 I/O 访问 

  • libavutil 包括哈希、解压缩等多样的功能 

  • libavfilter 提供了链式修改音频和视频的能力 

  • libavdevice 提供了对设备访问的抽象 

  • libswresample 实现了混音等能力 

  • libswscale 实现了颜色和尺度变换的能力

对外主要提供了三个工具:

  • ffmpeg 用来处理多媒体内容 

  • ffplay 是一个极简的播放器 

  • ffprobe 是多媒体内容的分析工具

于是,我们的视频转图片的功能,可以通过以下命令来实现,

ffmpeg -i clipname -vf fps=framerate -qscale:v 2 "imagename%04d.jpg"

具体来说,上面的指令可以把一个视频,按照固定的频率生成图片。

人脸定位 

基本算法

人脸定位是一个相对成熟的领域,主要应用 dlib 库的相关功能。我们虽然可以定制一个人脸识别的算法,但是我们也可以使用已有的通用的人脸识别 的函数库。

有两类算法,一类是 HOG 的脸部标记算法。

(来源: Facial landmarks with dlib, OpenCV, and Python) 

该算法的效果如上图。它将人脸分成了如下的区域:

  • 眼睛 (左/右)

  • 眉毛 (左/右)

  • 鼻子

  • 下巴 

基于这些标记,我们不仅能够进行后续的换脸,也能检测脸的具体形态,眨眼状态等。例如,我们可以把这些点连在一起,得到更多的特征。

(来源: Real-Time Face Pose Estimation ) 

寻找脸部标记是一个预测问题,输入是一张图片和兴趣区域,输出是兴趣区域的关键点。

HOG 是如何找到人脸的呢? 这是一个通用的检测算法:

  • 从数据集中找到正样本,并且计算 HOG 描述 

  • 从数据集中找到负样本,并且计算 HOG 描述 

  • 基于 HOG 的描述使用分类算法 

  • 在负样本上在不同的起点和尺度进行分类,并且找到误判的 HOG 

  • 基于上一步的负样本,对模型进行重新的训练

这里有个问题,如何计算 HOG 的描述呢? 我们可以计算每个点的亮度,然后把每个点表示为指向更黑的方向的向量。如下图所示:

 (来源: Machine Learning is Fun! Part 4: Modern Face Recognition with Deep Learning )


 (来源: Machine Learning is Fun! Part 4: Modern Face Recognition with Deep Learning )

我们为什么要这么做呢? 因为每个点的绝对值会受到环境的影响,但是相对值则比较稳定。因此,我们通过梯度变化的表示,能够准备出高质量的数据。当然,我们也可以进一步的把相邻的点聚合在一起,从而产生更有代表性的数据。

现在可以进行检测了

  • 首先在新的图片上基于不同的起点和尺度寻找可行的区间;

  • 基于非极大抑制的方法来减少冗余和重复的,下图就是一个有冗余和去除冗余的情况,这个方法说白了就是找一个最大概率的矩阵去覆盖掉和它过于重合的矩阵,并且不断重复这个过程。

  (来源: Histogram of Oriented Gradients and Object Detection)

有了轮廓之后,我们可以找到脸部标记。寻找脸部标记的算法是基于《One Millisecond Face Alignment with an Ensemble of Regression Trees》的论文。简单来说,它利用了已经标记好的训练集来训练一个回归树的组合,从而用来预测。

 (来源: One Millisecond Face Alignment with an Ensemble of Regression Trees) 

在这个基础上,就能够标记出这 68 个点。

 (来源: Facial landmarks with dlib, OpenCV, and Python ) 

基于人脸的 68 个标记的坐标,可以计算人脸的⻆度,从而抠出摆正后的人脸。但是 dlib 要求识别的必须是全脸,因此会减少我们的样本集以及一些特定的样本场景。同时,因为人脸是 64*64 像素的尺寸,因此也要处理清晰度的问题。

另一种方法是用 CNN 训练一个识别脸部的模型。CNN 能够检测更多的⻆度,但是需要更多的资源,并且可能在大文件上失效。

数据准备

我们的目标是把原始人脸转换为目标人脸,因此我们需要收集原始人脸的图片和目标人脸的图片。如果你选择的是一个名人,那么可以直接用 Google image 得到你想要的图片。虽然视频中的图片也能用,但是也可以收集一些多样的数据。当然,我用的是我和我老婆的图片,因此直接从我 们的 Photo 中导出即可。当人脸数据生成后,最好仔细检查一下,避免不应该的脸或者其它的东东出现在你的训练集中。

extract.py

Deepfake 用于定位人脸的算法如下:

import cv2 # 开源的计算机视觉库 from pathlib import Path # 提供面向对象方式的文件访问 from tqdm import tqdm # 提供进度条显示功能 import os # 提供操作系统相关的访问 import numpy as np # 提供科学计算相关的功能 from lib.cli import DirectoryProcessor, rotate_image # 处理一个目录的文件,然后保存到新的目录中;旋转图片,其实是在utils中 from lib.utils import get_folder # 获得一个folder,不存在则创建 from lib.multithreading import pool_process # 多进程并发计算 from lib.detect_blur import is_blurry # 判断图片是否模糊 from plugins.PluginLoader import PluginLoader # 加载对应的算法 class ExtractTrainingData(DirectoryProcessor): # 从训练集提取头像    def create_parser(self, subparser, command, description):        self.optional_arguments = self.get_optional_arguments()        self.parser = subparser.add_parser(            command,            help="Extract the faces from a pictures.",            description=description,            epilog="Questions and feedback: \            https://github.com/deepfakes/faceswap-playground"            ) # 参数配置部分省略    def process(self):        extractor_name = "Align" # 对应的是Extract_Align.py        self.extractor = PluginLoader.get_extractor(extractor_name)()        processes = self.arguments.processes        try:            if processes != 1: # 多进程处理图片                files = list(self.read_directory())                for filename, faces in tqdm(pool_process(self.processFiles, files, processes=processes), total = len(files)):                    self.num_faces_detected += 1                    self.faces_detected[os.path.basename(filename)] = faces            else: # 单进程处理图片                for filename in tqdm(self.read_directory()):                    try:                        image = cv2.imread(filename)                        self.faces_detected[os.path.basename(filename)] = self.handleImage(image, filename)                    except Exception as e:                        if self.arguments.verbose:                            print('Failed to extract from image: {}. Reason: {}'.format(filename, e))                        pass        finally:            self.write_alignments()    def processFiles(self, filename): # 处理一个单独的图片的函数        try:            image = cv2.imread(filename)            return filename, self.handleImage(image, filename)        except Exception as e:            if self.arguments.verbose:                print('Failed to extract from image: {}. Reason: {}'.format(filename, e))            pass        return filename, []    def getRotatedImageFaces(self, image, angle): # 得到固定角度旋转后的图片的人脸        rotated_image = rotate_image(image, angle)        faces = self.get_faces(rotated_image, rotation=angle)        rotated_faces = [(idx, face) for idx, face in faces]        return rotated_faces, rotated_image    def imageRotator(self, image): # 得到一系列旋转后的人脸        ''' rotates the image through rotation_angles to try to find a face '''        for angle in self.rotation_angles:            rotated_faces, rotated_image = self.getRotatedImageFaces(image, angle)            if len(rotated_faces) > 0:                if self.arguments.verbose:                    print('found face(s) by rotating image {} degrees'.format(angle))                break        return rotated_faces, rotated_image    def handleImage(self, image, filename):        faces = self.get_faces(image)        process_faces = [(idx, face) for idx, face in faces]        # 没有找到人脸,尝试旋转图片        if self.rotation_angles is not None and len(process_faces) == 0:            process_faces, image = self.imageRotator(image)        rvals = []        for idx, face in process_faces:            # 画出人脸的标记            if self.arguments.debug_landmarks:                for (x, y) in face.landmarksAsXY():                    cv2.circle(image, (x, y), 2, (0, 0, 255), -1)            resized_image, t_mat = self.extractor.extract(image, face, 256, self.arguments.align_eyes)            output_file = get_folder(self.output_dir) / Path(filename).stem            # 检测图片是否模糊            if self.arguments.blur_thresh is not None:                aligned_landmarks = self.extractor.transform_points(face.landmarksAsXY(), t_mat, 256, 48)                feature_mask = self.extractor.get_feature_mask(aligned_landmarks / 256, 256, 48)                feature_mask = cv2.blur(feature_mask, (10, 10))                isolated_face = cv2.multiply(feature_mask, resized_image.astype(float)).astype(np.uint8)                blurry, focus_measure = is_blurry(isolated_face, self.arguments.blur_thresh)                # print("{} focus measure: {}".format(Path(filename).stem, focus_measure))                # cv2.imshow("Isolated Face", isolated_face)                # cv2.waitKey(0)                # cv2.destroyAllWindows()                if blurry:                    print("{}'s focus measure of {} was below the blur threshold, moving to \"blurry\"".format(Path(filename).stem, focus_measure))                    output_file = get_folder(Path(self.output_dir) / Path("blurry")) / Path(filename).stem            cv2.imwrite('{}_{}{}'.format(str(output_file), str(idx), Path(filename).suffix), resized_image) # 生成新图片            f = {                "r": face.r,                "x": face.x,                "w": face.w,                "y": face.y,                "h": face.h,                "landmarksXY": face.landmarksAsXY()            }            rvals.append(f)        return rvals

 注意,基于特征标记的算法对于倾斜的脸效果不好,也可以引入 CNN。

人脸转换

人脸转换的基本原理是什么? 假设让你盯着一个人的视频连续看上 100 个小时,接着又给你看一眼另外一个人的照片,接着让你凭着记忆画出来刚才 的照片,你一定画的会很像第一个人的。

我们使用的模型是 Autoencoder。有趣的是,这个模型所做的是基于原始的图片再次生成原始的图片。Autoencoder 的编码器把图片进行压缩,而解 码器把图片进行还原,一个示例如下图:

(来源: Building Autoencoders in Keras ) 

在这个基础上,即使我们输入的是另外一个人脸,也会被 Autoencoder 编码成为一个类似原来的脸。

为了提升我们最终的效果,我们还需要把人脸共性相关的属性和人脸特性相关的属性进行学习。因此,我们对所有的脸都用一个统一的编码器,这 个编码器的目的是学习人脸共性的地方;然后,我们对每个脸有一个单独的解码器,这个解码器是为了学习人脸个性的地方。这样当你用 B 的脸通过 编码器,再使用 A 的解码器的话,你会得到一个与 B 的表情一致,但是 A 的脸。

这个过程用公式表示如下:

X' = Decoder(Encoder(Shuffle(X))) Loss = L1Loss(X'-X) A' = Decoder_A(Encoder(Shuffle(A))) Loss_A = L1Loss(A'-A) B' = Decoder_B(Encoder(Shuffle(B))) Loss_B = L1Loss(B'-B)

具体来说,在训练过程中,我们输入 A 的图片,通过编码器和解码器还原 A 的脸;然后我们输入 B 的图片,通过相同的编码器但是不同的解码器还原 B 的脸。不断迭代这个过程,直到 loss 降低到一个阈值。在模型训练的时候,我建议把 loss 降低到 0.02,这样的效果会比较好。

这里用的是比较标准的建模方式。值得注意的是,作者通过加入 PixelShuffler() 的函数把图像进行了一定的扭曲,而这个扭曲增加了学习的难度,反 而让模型能够实现最终的效果。仔细想想这背后的道理,如果你一直在做简单的题目,那么必然不会有什么解决难题的能力。但是,我只要把题目 做一些变体,就足以让你成⻓。

因为在建模中使用的是原图 A 的扭曲来还原 A,应用中是用 B 来还原 A,所以扭曲的方式会极大的影响到最终的结果。因此,如何选择更好的扭曲方 式,也是一个重要的问题。

当我们图片融合的时候,会有一个难题,如何又保证效果又防止图片抖动。于是我们还要引入相关的算法处理这些情况。于是我们可以知道,一个 看似直接的人脸转换算法在实际操作中需要考虑各种各样的特殊情况,这才是真真的接地气。

train.py

以下是进行训练的算法逻辑:

import cv2 # 开源的计算机视觉库 import numpy # 提供科学计算相关的功能 import time # 提供时间相关的功能 import threading # 提供多线程相关的功能 from lib.utils import get_image_paths, get_folder # 得到一个目录下的图片;获得一个folder,不存在则创建 from lib.cli import FullPaths, argparse, os, sys from plugins.PluginLoader import PluginLoader # 加载对应的算法 tf = None set_session = None def import_tensorflow_keras(): # 在需要的时候载入TensorFlow和keras模块    ''' Import the TensorFlow and keras set_session modules only when they are required '''    global tf    global set_session    if tf is None or set_session is None:        import tensorflow        import keras.backend.tensorflow_backend # keras依赖底层的tensorflow实现具体的运算        tf = tensorflow        set_session = keras.backend.tensorflow_backend.set_session class TrainingProcessor(object): # 训练器    arguments = None    def __init__(self, subparser, command, description='default'): # 初始化训练器        self.argument_list = self.get_argument_list()        self.optional_arguments = self.get_optional_arguments()        self.parse_arguments(description, subparser, command)        self.lock = threading.Lock()    def process_arguments(self, arguments):        self.arguments = arguments        print("Model A Directory: {}".format(self.arguments.input_A))        print("Model B Directory: {}".format(self.arguments.input_B))        print("Training data directory: {}".format(self.arguments.model_dir))        self.process() # 参数配置部分省略    @staticmethod    def get_optional_arguments(): # 创建一个存放参数的数组        ''' Put the arguments in a list so that they are accessible from both argparse and gui '''        # Override this for custom arguments        argument_list = []        return argument_list    def parse_arguments(self, description, subparser, command):        parser = subparser.add_parser(            command,            help="This command trains the model for the two faces A and B.",            description=description,            epilog="Questions and feedback: \            https://github.com/deepfakes/faceswap-playground")        for option in self.argument_list:            args = option['opts']            kwargs = {key: option[key] for key in option.keys() if key != 'opts'}            parser.add_argument(*args, **kwargs)        parser = self.add_optional_arguments(parser)        parser.set_defaults(func=self.process_arguments)    def add_optional_arguments(self, parser):        for option in self.optional_arguments:            args = option['opts']            kwargs = {key: option[key] for key in option.keys() if key != 'opts'}            parser.add_argument(*args, **kwargs)        return parser    def process(self): # 具体的执行        self.stop = False        self.save_now = False        thr = threading.Thread(target=self.processThread, args=(), kwargs={}) # 线程执行        thr.start()        if self.arguments.preview:            print('Using live preview')            while True:                try:                    with self.lock:                        for name, image in self.preview_buffer.items():                            cv2.imshow(name, image)                    key = cv2.waitKey(1000)                    if key == ord('\n') or key == ord('\r'):                        break                    if key == ord('s'):                        self.save_now = True                except KeyboardInterrupt:                    break        else:            try:                input() # TODO how to catch a specific key instead of Enter?                # there isnt a good multiplatform solution: https://stackoverflow.com/questions/3523174/raw-input-in-python-without-pressing-enter            except KeyboardInterrupt:                pass        print("Exit requested! The trainer will complete its current cycle, save the models and quit (it can take up a couple of seconds depending on your training speed). If you want to kill it now, press Ctrl + c")        self.stop = True        thr.join() # waits until thread finishes    def processThread(self):        try:            if self.arguments.allow_growth:                self.set_tf_allow_growth()            print('Loading data, this may take a while...') # 加载数据            # this is so that you can enter case insensitive values for trainer            trainer = self.arguments.trainer            trainer = "LowMem" if trainer.lower() == "lowmem" else trainer            model = PluginLoader.get_model(trainer)(get_folder(self.arguments.model_dir), self.arguments.gpus) # 读取模型            model.load(swapped=False)            images_A = get_image_paths(self.arguments.input_A) # 图片A            images_B = get_image_paths(self.arguments.input_B) # 图片B            trainer = PluginLoader.get_trainer(trainer) # 创建训练器            trainer = trainer(model, images_A, images_B, self.arguments.batch_size, self.arguments.perceptual_loss) # 设置训练器参数            print('Starting. Press "Enter" to stop training and save model')            for epoch in range(0, self.arguments.epochs):                save_iteration = epoch % self.arguments.save_interval == 0                trainer.train_one_step(epoch, self.show if (save_iteration or self.save_now) else None) # 进行一步训练                if save_iteration:                    model.save_weights()                if self.stop:                    break                if self.save_now:                    model.save_weights()                    self.save_now = False            model.save_weights()            exit(0)        except KeyboardInterrupt:            try:                model.save_weights()            except KeyboardInterrupt:                print('Saving model weights has been cancelled!')            exit(0)        except Exception as e:            raise e            exit(1)    def set_tf_allow_growth(self):        import_tensorflow_keras()        config = tf.ConfigProto()        config.gpu_options.allow_growth = True        config.gpu_options.visible_device_list="0"        set_session(tf.Session(config=config))    preview_buffer = {}    def show(self, image, name=''): # 提供预览        try:            if self.arguments.redirect_gui:                scriptpath = os.path.realpath(os.path.dirname(sys.argv[0]))                img = '.gui_preview.png'                imgfile = os.path.join(scriptpath, img)                cv2.imwrite(imgfile, image)            elif self.arguments.preview:                with self.lock:                    self.preview_buffer[name] = image            elif self.arguments.write_image:                cv2.imwrite('_sample_{}.jpg'.format(name), image)        except Exception as e:            print("could not preview sample")            raise e

Trainer.py

以下实现了一次具体的训练:

import time import numpy from lib.training_data import TrainingDataGenerator, stack_images class Trainer():    random_transform_args = { # 初始化参数        'rotation_range': 10,        'zoom_range': 0.05,        'shift_range': 0.05,        'random_flip': 0.4,    }    def __init__(self, model, fn_A, fn_B, batch_size, *args):        self.batch_size = batch_size        self.model = model        generator = TrainingDataGenerator(self.random_transform_args, 160) # 读取需要的数据        self.images_A = generator.minibatchAB(fn_A, self.batch_size)        self.images_B = generator.minibatchAB(fn_B, self.batch_size)    def train_one_step(self, iter, viewer): # 训练一步        epoch, warped_A, target_A = next(self.images_A)        epoch, warped_B, target_B = next(self.images_B)        loss_A = self.model.autoencoder_A.train_on_batch(warped_A, target_A) # 计算损失        loss_B = self.model.autoencoder_B.train_on_batch(warped_B, target_B)        print("[{0}] [#{1:05d}] loss_A: {2:.5f}, loss_B: {3:.5f}".format(time.strftime("%H:%M:%S"), iter, loss_A, loss_B),            end='\r')        if viewer is not None:            viewer(self.show_sample(target_A[0:14], target_B[0:14]), "training")    def show_sample(self, test_A, test_B):        figure_A = numpy.stack([            test_A,            self.model.autoencoder_A.predict(test_A),            self.model.autoencoder_B.predict(test_A),        ], axis=1)        figure_B = numpy.stack([            test_B,            self.model.autoencoder_B.predict(test_B),            self.model.autoencoder_A.predict(test_B),        ], axis=1)        if test_A.shape[0] % 2 == 1:            figure_A = numpy.concatenate ([figure_A, numpy.expand_dims(figure_A[0],0) ])            figure_B = numpy.concatenate ([figure_B, numpy.expand_dims(figure_B[0],0) ])        figure = numpy.concatenate([figure_A, figure_B], axis=0)        w = 4        h = int( figure.shape[0] / w)        figure = figure.reshape((w, h) + figure.shape[1:])        figure = stack_images(figure)        return numpy.clip(figure * 255, 0, 255).astype('uint8')

AutoEncoder.py

以下是我们使用的AutoEncoder的算法逻辑:

# AutoEncoder的基础类 import os, shutil encoderH5 = 'encoder.h5' decoder_AH5 = 'decoder_A.h5' decoder_BH5 = 'decoder_B.h5' class AutoEncoder:    def __init__(self, model_dir, gpus):        self.model_dir = model_dir        self.gpus = gpus        self.encoder = self.Encoder()        self.decoder_A = self.Decoder()        self.decoder_B = self.Decoder()        self.initModel()    def load(self, swapped):        (face_A,face_B) = (decoder_AH5, decoder_BH5) if not swapped else (decoder_BH5, decoder_AH5)        try: # 加载权重            self.encoder.load_weights(str(self.model_dir / encoderH5))            self.decoder_A.load_weights(str(self.model_dir / face_A))            self.decoder_B.load_weights(str(self.model_dir / face_B))            print('loaded model weights')            return True        except Exception as e:            print('Failed loading existing training data.')            print(e)            return False    def save_weights(self): # 存储权重        model_dir = str(self.model_dir)        if os.path.isdir(model_dir + "_bk"):            shutil.rmtree(model_dir + "_bk")        shutil.move(model_dir,  model_dir + "_bk")        os.mkdir(model_dir)        self.encoder.save_weights(str(self.model_dir / encoderH5))        self.decoder_A.save_weights(str(self.model_dir / decoder_AH5))        self.decoder_B.save_weights(str(self.model_dir / decoder_BH5))        print('saved model weights')

Model.py

以下是我们的具体模型:

# Based on the original https://www.reddit.com/r/deepfakes/ code sample + contribs from keras.models import Model as KerasModel from keras.layers import Input, Dense, Flatten, Reshape from keras.layers.advanced_activations import LeakyReLU from keras.layers.convolutional import Conv2D from keras.optimizers import Adam from .AutoEncoder import AutoEncoder from lib.PixelShuffler import PixelShuffler from keras.utils import multi_gpu_model IMAGE_SHAPE = (64, 64, 3) ENCODER_DIM = 1024 class Model(AutoEncoder):    def initModel(self):        optimizer = Adam(lr=5e-5, beta_1=0.5, beta_2=0.999)  # 深入理解Adam的优化        x = Input(shape=IMAGE_SHAPE)        self.autoencoder_A = KerasModel(x, self.decoder_A(self.encoder(x)))        self.autoencoder_B = KerasModel(x, self.decoder_B(self.encoder(x)))        if self.gpus > 1:            self.autoencoder_A = multi_gpu_model( self.autoencoder_A , self.gpus)            self.autoencoder_B = multi_gpu_model( self.autoencoder_B , self.gpus)        self.autoencoder_A.compile(optimizer=optimizer, loss='mean_absolute_error')        self.autoencoder_B.compile(optimizer=optimizer, loss='mean_absolute_error')    def converter(self, swap):        autoencoder = self.autoencoder_B if not swap else self.autoencoder_A        return lambda img: autoencoder.predict(img)    def conv(self, filters):        def block(x):            x = Conv2D(filters, kernel_size=5, strides=2, padding='same')(x)            x = LeakyReLU(0.1)(x)            return x        return block    def upscale(self, filters):        def block(x):            x = Conv2D(filters * 4, kernel_size=3, padding='same')(x)            x = LeakyReLU(0.1)(x) # 使用 LeakyReLU 激活函数            x = PixelShuffler()(x) # 将filter的大小变为原来的1/4,让高和宽变为原来的两倍            return x        return block    def Encoder(self):        input_ = Input(shape=IMAGE_SHAPE)        x = input_        x = self.conv(128)(x)        x = self.conv(256)(x)        x = self.conv(512)(x)        x = self.conv(1024)(x)        x = Dense(ENCODER_DIM)(Flatten()(x))        x = Dense(4 * 4 * 1024)(x)        x = Reshape((4, 4, 1024))(x)        x = self.upscale(512)(x)        return KerasModel(input_, x)    def Decoder(self):        input_ = Input(shape=(8, 8, 512))        x = input_        x = self.upscale(256)(x)        x = self.upscale(128)(x)        x = self.upscale(64)(x)        x = Conv2D(3, kernel_size=5, padding='same', activation='sigmoid')(x)        return KerasModel(input_, x)

整个网络的结构如下:

 来源: 刷爆朋友圈的视频人物换脸是怎样炼成的?

我们可以看出来,经历了四个卷积层、展开层、全连接层,我们开始 upscale 整个模型。在我们 upscale 一半的时候,我们把 encoder 和 decoder 进行了切割,从而保证了共性和个性的分离。

convert.py

在训练的基础上,我们现在可以进行图片的转换了。

import cv2 import re import os from pathlib import Path from tqdm import tqdm from lib.cli import DirectoryProcessor, FullPaths from lib.utils import BackgroundGenerator, get_folder, get_image_paths, rotate_image from plugins.PluginLoader import PluginLoader class ConvertImage(DirectoryProcessor):    filename = ''    def create_parser(self, subparser, command, description):        self.optional_arguments = self.get_optional_arguments()        self.parser = subparser.add_parser(            command,            help="Convert a source image to a new one with the face swapped.",            description=description,            epilog="Questions and feedback: \            https://github.com/deepfakes/faceswap-playground"        ) # 参数配置部分省略    def process(self): # 进行模型的转换和拼接        # Original & LowMem models go with Adjust or Masked converter        # Note: GAN prediction outputs a mask + an image, while other predicts only an image        model_name = self.arguments.trainer        conv_name = self.arguments.converter        self.input_aligned_dir = None        model = PluginLoader.get_model(model_name)(get_folder(self.arguments.model_dir), self.arguments.gpus)        if not model.load(self.arguments.swap_model):            print('Model Not Found! A valid model must be provided to continue!')            exit(1)        input_aligned_dir = Path(self.arguments.input_dir)/Path('aligned')        if self.arguments.input_aligned_dir is not None:            input_aligned_dir = self.arguments.input_aligned_dir        try:            self.input_aligned_dir = [Path(path) for path in get_image_paths(input_aligned_dir)]            if len(self.input_aligned_dir) == 0:                print('Aligned directory is empty, no faces will be converted!')            elif len(self.input_aligned_dir) <= len(self.input_dir)/3:                print('Aligned directory contains an amount of images much less than the input, are you sure this is the right directory?')        except:            print('Aligned directory not found. All faces listed in the alignments file will be converted.')        converter = PluginLoader.get_converter(conv_name)(model.converter(False),            trainer=self.arguments.trainer,            blur_size=self.arguments.blur_size,            seamless_clone=self.arguments.seamless_clone,            sharpen_image=self.arguments.sharpen_image,            mask_type=self.arguments.mask_type,            erosion_kernel_size=self.arguments.erosion_kernel_size,            match_histogram=self.arguments.match_histogram,            smooth_mask=self.arguments.smooth_mask,            avg_color_adjust=self.arguments.avg_color_adjust        )        batch = BackgroundGenerator(self.prepare_images(), 1)        # frame ranges stuff...        self.frame_ranges = None        # split out the frame ranges and parse out "min" and "max" values        minmax = {            "min": 0, # never any frames less than 0            "max": float("inf")        }        if self.arguments.frame_ranges:            self.frame_ranges = [tuple(map(lambda q: minmax[q] if q in minmax.keys() else int(q), v.split("-"))) for v in self.arguments.frame_ranges]        # last number regex. I know regex is hacky, but its reliablyhacky(tm).        self.imageidxre = re.compile(r'(\d+)(?!.*\d)')        for item in batch.iterator():            self.convert(converter, item)    def check_skipframe(self, filename):        try:            idx = int(self.imageidxre.findall(filename)[0])            return not any(map(lambda b: b[0]<=idx<=b[1], self.frame_ranges))        except:            return False    def check_skipface(self, filename, face_idx):        aligned_face_name = '{}_{}{}'.format(Path(filename).stem, face_idx, Path(filename).suffix)        aligned_face_file = Path(self.arguments.input_aligned_dir) / Path(aligned_face_name)        # TODO: Remove this temporary fix for backwards compatibility of filenames        bk_compat_aligned_face_name = '{}{}{}'.format(Path(filename).stem, face_idx, Path(filename).suffix)        bk_compat_aligned_face_file = Path(self.arguments.input_aligned_dir) / Path(bk_compat_aligned_face_name)        return aligned_face_file not in self.input_aligned_dir and bk_compat_aligned_face_file not in self.input_aligned_dir    def convert(self, converter, item):        try:            (filename, image, faces) = item            skip = self.check_skipframe(filename)            if self.arguments.discard_frames and skip:                return            if not skip: # process frame as normal                for idx, face in faces:                    if self.input_aligned_dir is not None and self.check_skipface(filename, idx):                        print ('face {} for frame {} was deleted, skipping'.format(idx, os.path.basename(filename)))                        continue                    # Check for image rotations and rotate before mapping face                    if face.r != 0:                        height, width = image.shape[:2]                        image = rotate_image(image, face.r)                        image = converter.patch_image(image, face, 64 if "128" not in self.arguments.trainer else 128)                        # TODO: This switch between 64 and 128 is a hack for now. We should have a separate cli option for size                        image = rotate_image(image, face.r * -1, rotated_width=width, rotated_height=height)                    else:                        image = converter.patch_image(image, face, 64 if "128" not in self.arguments.trainer else 128)                        # TODO: This switch between 64 and 128 is a hack for now. We should have a separate cli option for size            output_file = get_folder(self.output_dir) / Path(filename).name            cv2.imwrite(str(output_file), image)        except Exception as e:            print('Failed to convert image: {}. Reason: {}'.format(filename, e))    def prepare_images(self):        self.read_alignments()        is_have_alignments = self.have_alignments()        for filename in tqdm(self.read_directory()):            image = cv2.imread(filename)            if is_have_alignments:                if self.have_face(filename):                    faces = self.get_faces_alignments(filename, image)                else:                    tqdm.write ('no alignment found for {}, skipping'.format(os.path.basename(filename)))                    continue            else:                faces = self.get_faces(image)            yield filename, image, faces

当然我们也可以用 GAN 算法进行优化,那么让我们看一下使用 GAN 的模型。

 (来源: shaoanlu/faceswap-GAN) 

如上图所示,我们首先扣取 A 的人脸,然后进行变形,之后经历编码和解码生成了重建的脸和 Mask。以下是我们的学习目标。

 (来源: shaoanlu/faceswap-GAN) 

从图片到视频

基于我们 FFmpeg 的讲解,可以使用以下命令将一批图片合并为一个视频:

ffmpeg  -f image2 -i imagename%04d.jpg -vcodec libx264 -crf 15 -pix_fmt yuv420p output_filename.mp4

如果你希望新生成的视频有声音,那就可以在最后把有声音的视频中的声音拼接到你最后产生的目标视频上即可。

云平台部署

我们可以在 Google Cloud 中部署云平台。具体请看视频展示,我在这里展示几个关键步骤:

 (来源: How to Create DeepFakes with Google Cloud GPU Services)


 (来源: How to Create DeepFakes with Google Cloud GPU Services)

 (来源: How to Create DeepFakes with Google Cloud GPU Services)


 (来源: How to Create DeepFakes with Google Cloud GPU Services) 

最后是我在 Google Cloud 上进行 Training 的一个截图。

项目架构

最后让我们从高层理解一下整个 DeepFake 项目的架构。

社会影响

我们已经聊了 Deepfake 的原理,那么它到底有哪些真正的社会价值呢? 我们可以用任何人来拍摄一个电影,然后变成我们想要的任何人。我们可以 创建更加真实的虚拟人物。穿衣购物可以更加真人模拟。

总结

我们用到了如下的技术栈、框架、平台:

  • Dlib:基于 C++的机器学习算法库 OpenCV:计算机视觉算法库 Keras:在底层机器学习框架之上的高级 API 架构 TensorFlow:Google 开源的机器学习算法框架 CUDA:Nvidia 提供的针对 GPU 加速的开发环境

  • Google Cloud Platform:Google 提供的云计算服务平台 Virtualenv:创建独立的 Python 环境 FFmpeg:多媒体音视频处理开源库

  • 现在就来上手,把你心爱的另一半的人脸搬上好莱坞吧。

]]> 原文: https://ift.tt/2FFt6Ob
RSS Feed

机器知心

IFTTT

RNN和LSTM弱!爆!了!注意力模型才是王道

循环神经网络( RNN ),长短期记忆( LSTM ),这些红得发紫的神经网络——是时候抛弃它们了!

LSTM 和 RNN 被发明于上世纪80、90年代,于2014年死而复生。接下来的几年里,它们成为了解决序列学习、序列转换( seq2seq )的方式,这也使得语音到文本识别和 Siri 、Cortana、Google 语音助理、Alexa 的能力得到惊人的提升。

另外,不要忘了机器翻译,包括将文档翻译成不同的语言,或者是神经网络机器翻译还可以将图像翻译为文本,文字到图像和字幕视频等等。

在接下来的几年里,ResNet 出现了。ResNet 是残差网络,意为训练更深的模型。2016年,微软亚洲研究院的一组研究员在 ImageNet 图像识别挑战赛中凭借惊人的152层深层残差网络( deep residual networks ),以绝对优势获得图像分类、图像定位以及图像检测全部三个主要项目的冠军。之后,Attention(注意力)模型出现了。

虽然仅仅过去两年,但今天我们可以肯定地说:

"不要再用 RNN 和 LSTM 了,它们已经不行了!"

让我们用事实说话。Google、Facebook、Salesforce 等企业越来越多地使用了基于注意力模型( Attention )的网络。

所有这些企业已经将 RNN 及其变种替换为基于注意力的模型,而这仅仅是个开始。比起基于注意力的模型,RNN 需要更多的资源来训练和运行。RNN 命不久矣。

为什么

记住 RNN 和 LSTM 及其衍生主要是随着时间推移进行顺序处理。请参阅下图中的水平箭头:


RNN中的顺序处理

水平箭头的意思是长期信息需在进入当前处理单元前顺序遍历所有单元。这意味着其能轻易被乘以很多次<0的小数而损坏。这是导致 vanishing gradients (梯度消失)问题的原因。

为此,今天被视为救星的 LSTM 模型出现了,有点像 ResNet 模型,可以绕过单元从而记住更长的时间步骤。因此,LSTM 可以消除一些梯度消失的问题。


LSTM中的顺序处理


从上图可以看出,这并没有解决全部问题。我们仍然有一条从过去单元到当前单元的顺序路径。事实上,这条路现在更复杂了,因为它有附加物,并且忽略了隶属于它上面的分支。

毫无疑问 LSTM 和 GRU(Gated Recurrent Uni,是 LSTM 的衍生)及其衍生能够记住大量更长期的信息!但是它们只能记住100个量级的序列,而不是1000个量级,或者更长的序列。

还有一个 RNN 的问题是,训练它们对硬件的要求非常高。另外,在我们不需要训练这些网络快速的情况下,它仍需要大量资源。同样在云中运行这些模型也需要很多资源。

考虑到语音到文本的需求正在迅速增长,云是不可扩展的。我们需要在边缘处进行处理,比如 Amazon Echo 上处理数据。

该做什么?

如果要避免顺序处理,那么我们可以找到"前进"或更好"回溯"单元,因为大部分时间我们处理实时因果数据,我们"回顾过去"并想知道其对未来决定的影响("影响未来")。在翻译句子或分析录制的视频时并非如此,例如,我们拥有完整的数据,并有足够的处理时间。这样的回溯/前进单元是神经网络注意力( Neural Attention )模型组。

为此,通过结合多个神经网络注意力模型,"分层神经网络注意力编码器"出现了,如下图所示:


分层神经网络注意力编码器


"回顾过去"的更好方式是使用注意力模型将过去编码向量汇总到语境矢量  CT 中。

请注意上面有一个注意力模型层次结构,它和神经网络层次结构非常相似。这也类似于下面的备注3中的时间卷积网络( TCN )。

在分层神经网络注意力编码器中,多个注意力分层可以查看最近过去的一小部分,比如说100个向量,而上面的层可以查看这100个注意力模块,有效地整合 100 x 100 个向量的信息。这将分层神经网络注意力编码器的能力扩展到10,000个过去的向量。

这才是"回顾过去"并能够"影响未来"的正确方式!

但更重要的是查看表示向量传播到网络输出所需的路径长度:在分层网络中,它与  log( N )成正比,其中N是层次结构层数。这与 RNN 需要做的T步骤形成对比,其中T是要记住的序列的最大长度,并且 T >> N。

跳过 3-4 步追溯信息比跳过 100 步要简单多了!

这种体系结构跟神经网络图灵机很相似,但可以让神经网络通过注意力决定从内存中读出什么。这意味着一个实际的神经网络将决定哪些过去的向量对未来决策有重要性。

但是存储到内存怎么样呢?上述体系结构将所有先前的表示存储在内存中,这与神经网络图灵机( NTM )不同。这可能是相当低效的:考虑将每帧的表示存储在视频中——大多数情况下,表示向量不会改变帧到帧,所以我们确实存储了太多相同的内容!

我们可以做的是添加另一个单元来防止相关数据被存储。例如,不存储与以前存储的向量太相似的向量。但这确实只是一种破解的方法,最好的方法是让应用程序指导哪些向量应该保存或不保存。这是当前研究的重点。

看到如此多的公司仍然使用 RNN/LSTM 进行语音到文本的转换,我真的十分惊讶。许多人不知道这些网络是如此低效和不可扩展。

训练  RNN  和  LSTM  的噩梦

RNN 和 LSTM 的训练是困难的,因为它们需要存储带宽绑定计算,这是硬件设计者最糟糕的噩梦,最终限制了神经网络解决方案的适用性。简而言之, LSTM 需要每个单元4个线性层( MLP 层)在每个序列时间步骤中运行。

线性层需要大量的存储带宽来计算,事实上,它们不能使用许多计算单元,通常是因为系统没有足够的存储带宽来满足计算单元。而且很容易添加更多的计算单元,但是很难增加更多的存储带宽(注意芯片上有足够的线,从处理器到存储的长电线等)。

因此,RNN/LSTM 及其变种不是硬件加速的良好匹配,我们在这里之前和这里都讨论过这个问题。一个解决方案将在存储设备中计算出来,就像我们在 FWDNXT 上工作的一样。

总而言之,抛弃 RNN 吧。注意力模型真的就是你需要的一切!

]]> 原文: https://ift.tt/2HQJJbz
RSS Feed

机器知心

IFTTT

牛津大学提出神经网络新训练法:用低秩结构增强网络压缩和对抗稳健性

和目前普遍的稀疏性诱导、结构化限制相似,神经网络的低秩结构也具有压缩的性质,并在对抗攻击中具备稳健性。在本文中,来自牛津大学计算科学部和阿兰图灵机构的研究者开发了一种新方法,通过在训练过程中引入修正,增强神经网络表征的低秩属性。

引言

深度(卷积)神经网络已经取得了许多重大成果,「表征学习」就是其中非常迷人的一个方面:深度网络能够从原始数据中生成可以用于多个任务的表征。有趣的是,从奠基性论文 Krizhevsky et al. (2012) 开始,人们发现,即使是在完全的监督学习体系下训练出的神经网络也具有这一性质。在其他和分类、检索、聚类(通常和原始的分类问题无关)等相关领域,人们利用这些学得的表征(即迁移学习)已经取得了巨大的成功(Kiros et al., 2014; Lin and Parikh, 2015)。

从本质上讲,可以认为倒数第二层(或接近输出层的某一层)神经元的激活就是原始数据的一个习得表征(learned representation)(也就是希望从这张图像中希望得到的内容)。而最后一层神经元通常只是一个多类别 logistic 回归模型。在本文中,作者主要研究了 ResNet-18 和 ResNet-50(He et al., 2016),同时也部分包括 VGG 网络(Simonyan and Zisserman, 2014)上的研究结果。尽管近年来许多人广泛研究了神经网络架构的方方面面,但几乎没有关于如何理解这些表征本质的相关工作。

本文研究了这些习得表征,主要探索了其(有效)维度问题。一个 ResNet-18/50 网络基本都是由 4 个 ResNet 块(block)组成(其中每个块又包含了多个卷积层和跳过连接)。我们探索的是第 3、第 4 个 ResNet 块末端激活的维度。在 ResNet-18 中,第 3 个 ResNet 块后的激活维度为 16384,第 4 个 ResNet 块后的激活维度则为 512。在 ResNet-50 中,作者只研究了最后一个 ResNet 块后的激活维度:为 2048。在实验中,每一个数据点 x 都映射为向量 a ∈ R^m,用 d 表示上述层(layer)中的激活数量;而向量 a 则是 x 的一个习得表征。实证研究(Oyallon, 2017)表明:给定类别,这些习得表征(近似)处于一个低秩(仿射)空间中。(Oyallon 2017 年的研究中,使用了另一个不同的卷积神经网络来处理图像分类问题)。

作者对训练过程进行了修正,以保证激活可以(近似)处于一个低秩空间中;准确的说,他们在损失函数中加入了一项,以促使特定层的激活能够处于低秩仿射空间。使用修正后训练过程得到的结果准确率基本没有下降(在一些场景下甚至有少量提升),同时增强了习得特征的低秩属性。修正在模型中「加入」了一个虚拟的(virtual)低秩层,可以保证习得特征基本处于低秩空间中。在优化修正后的目标函数时,使用的是交替最小化方法,该想法类似于迭代硬阈值(Blumensath and Davies, 2009)或奇异值投影(Jain et al., 2010)中所使用的方法。

考虑到朴素奇异值阈值方法会使得训练过程无法满足任何实际场景下的需要,作者采用了基于 Nystr¨om 方法(Williams and Seeger, 2001; Halko et al., 2011)的列采样方法,训练速度得到了显著的提升,但也使得没有得到最优的低秩映射。可以认为,修正后的训练过程能够防止神经网络出现过度参数化(over-parametrization),不过使用了和目前普遍的稀疏性诱导方法(如 Anwar et al. (2017); Wen et al. (2016))以及结构化限制方法(Moczulski et al. (2015); Liu et al. (2015))都不同的手段。

最后,作者也探索了学习低秩表征的优点。其中一个明显的优点是在其它的应用场景中,低秩表征能够压缩嵌入:事实上,由于这些习得表征(近似)处于一个低维(仿射)空间中,它们本身就满足一种压缩框架。另外,我们研究了这种方式训练出的神经网络在对抗性攻击(Szegedy et al., 2013)下的稳健性。结果显示,相比于标准架构,这些神经网络基本上对由 GSM 方法(Gradient Sign Method)及其变体(Kurakin et al., 2016)生成的对抗性攻击有更好的稳健性。实证评估进一步表明,在使用习得表征(或其低秩投影)来训练 SVM 分类器时,利用修正方法训练得到的神经网络在使用习得表征低秩投影时,可以给出更准确的预测结果。

3 LR-Layered 网络

4.1 模型性能没有下降

表 1:不同的 ResNet 模型在 CIFAR-10 上的测试准确率。

表 2:ResNet 模型在 CIFAR-100 上的测试准确率:包含原始结果和迁移到 Fine Label 后的结果。

4.2 方差率捕获

图 1:倒数第二层上的方差率(Variance Ratio)。

图 2:第 4 个 ResNet 块前的层上的方差率。

4.4 低维嵌入的有效性

表 3:低维嵌入准确率:利用 CIFAR-100 的超类训练 ResNet-50,在最后一个全连接层前的激活上生成低维嵌入。

表 4:低维嵌入准确率:利用 CIFAR-10 训练的 ResNet-18,利用最后一个 ResNet 块的嵌入生成低维嵌入。

5 对抗攻击


图 5:上图展示了对抗性的误分类和扰动量级间的关系。(扰动量级使用归一化 L2 差异度量。其中 1-LR 和 2-LR 分别表示 ResNet18-1-LR 和 ResNet18-2-LR。LR-V 和 N-LR-V 分别对应低秩 VGG19 模型和标准 VGG19 模型)。

图 6:CIFAR-100 超类标签的 PCA 图。左图展示了 ResNet-50 上训练的 LR 模型的嵌入结果,右图展示了标准的 ResNet-50 模型结果,两个模型使用了类似的训练方法。图中不同颜色表示不同类别。

论文:Low Rank Structure of Learned Representations(习得表征的低秩结构)

论文地址:https://arxiv.org/pdf/1804.07090.pdf

摘要:神经网络——尤其是深度卷积神经网络——有一个很重要的特征:它们能够从数据中学习出非常有用的表征,而最后一层神经元则只是在这些习得特征上训练的线性模型。虽然神经网络在其它诸如分类、检索、聚类等目标中得到了广泛使用(即迁移学习),但并没有足够的关于这些表征结构,或是否可以在训练过程中引入某些结构的相关研究结果。

本文选择了一些在图像分类问题中表现很好的神经网络,并研究了其习得表征的维度。我们选取了 ResNet-18、ResNet-50 以及 VGG-19,并使用 CIFAR10/CIFAR100 数据集来训练模型;我们发现,这些模型的习得表征表现出了明显的低秩结构。在训练过程中,我们引入了一定的修正,以促进神经网络不同阶段激活的低秩表征。实证结果表明,低秩结构具有压缩的性质,在对抗样本问题中,也具有更高的稳健性。

]]> 原文: https://ift.tt/2FFsyb5
RSS Feed

机器知心

IFTTT

全球AI芯片企业排行:英伟达第1,华为第12(七家中国公司入围Top24)

允中 夏乙 发自 凹非寺量子位 出品 | 公众号 QbitAI

AI芯片哪家强?

在调查研究了全球100多家企业后,市场研究和咨询公司Compass Intelligence发布了2018年度全球AI芯片公司排行榜。

在这份榜单上,英伟达排名第一。

这份报告也特别提到了英伟达丰富的产品线,包括用于数据中心的Volta GPU架构,自动驾驶平台NVIDIA DRIVE PX,DGX-1等等。

英特尔、IBM、Google、苹果、AMD、ARM、高通、三星、恩智浦等公司位列2-10名。

在Top24的榜单排行中,共有七家中国公司入围。

  • 华为(海思)位列这份榜单的第12位。
  • 联发科(MediaTek)排名第14位。
  • Imagination排名第15位。
  • 瑞芯微(Rockchip)排名第20位。
  • 芯原(Verisilcon)排名第21位。
  • 寒武纪(Cambricon)排名第23位。
  • 地平线(Horizon)排名第24位。

据介绍,这份一线AI芯片组排行榜包括提供AI芯片组软硬件的公司。

所谓AI芯片组产品包括:中央处理器(CPU)、图形处理器(GPU)、神经网络处理器(NNP)、专用集成电路(ASIC)、现场可编程门阵列(FPGA)、精简指令集(RISC)处理器、加速器等等。

以及针对边缘处理和设备的芯片组,云计算使用的服务器,针对机器视觉和自动驾驶平台的设备等。还包括AI的计算框架、训练平台等。

中国芯片军团

这七家入围的中国公司,有些公众很熟悉,有些相对低调。

我们先介绍几家相对低调一些的公司。

其中,Imagination原本是一家英国芯片企业,曾经是苹果公司的GPU供应商。去年9月,中国资本Canyon Bridge出资5.5亿英镑将其收购。

目前,Imagination公司CEO已由李力游出任,李力游此前曾任展讯的董事长,后展讯被紫光收购,李力游出任紫光集团联席总裁。

2017年9月,Imagination推出了面向人工智能应用的硬件IP产品PowerVR Series2NX NNA 神经网络加速器。

瑞芯微(Rockchip)成立于2001年,主要为智能手机、平板电脑、通讯平板,电视机顶盒、车载导航、IoT物联网等产品提供芯片解决方案。

据介绍,瑞芯微是一家做ARM应用处理器的公司,靠复读机和MP3/MP4芯片起家。今年一月,瑞芯微发布首款AI处理器RK3399Pro,NPU运算性能达2.4TOPs。

去年7月,瑞芯微创业板IPO未能过会,一度引发关注。

芯原(Verisilcon)也成立于2001年,自身定位是一家芯片设计平台即服务(Silicon Platform as a Service,SiPaaS)提供商。

去年5月,芯原推出一款面向计算机视觉和人工智能应用的处理器VIP8000,可以直接导入由Caffe和TensorFlow等框架生成的神经网络。恩智浦的旗舰应用处理器i.MX 8上也采用了芯原的视觉图形处理器。

2015年,芯原还收购了美国嵌入式图形处理器(GPU)设计商图芯技术(Vivante)。

芯原创始人戴伟民曾出任美国Celestry公司(2002年被Cadence并购)董事长兼CTO。

而华为、联发科、地平线这些公司,大家相对比较熟悉,或者量子位也有过不少报道,在这里不多赘言。

特别提一下,昨天寒武纪发布了最新的产品。

这也是两款早已经在路线图上公布过的产品。首先是寒武纪1M,这家公司的第三代机器学习专用芯片。1M使用TSMC 7nm工艺生产,其8位运算效能比达5Tops/watt(每瓦 5 万亿次运算)。

另外就是云端AI芯片MLU 100。这款芯片采用寒武纪MLUv01架构和TSMC 16nm工艺。在平衡模式(主频 1Ghz)和高性能模式(1.3GHz)主频下,其等效理论峰值速度则分别可以达到128万亿次定点运算/166.4万亿次定点运算,对应功耗为80w/110w。

"英特尔必须改"

最后,讲一个国外的芯片巨头。

开头的榜单上,英特尔携收购来的Mobileye、Nervana、Movidius,排在第二名。

"仅次于英伟达",对这家称霸芯片业数十年的巨头来说,可算不上什么值得夸耀的成就。

站在AI时代路口,英特尔其实已经很努力了。

这家巨头在数据中心芯片市场占据着90%的份额,这几年,数据中心所处理的计算任务随着人工智能兴起而逐渐改变,英伟达成了时代宠儿,英特尔深感地位岌岌可危。

于是,英特尔一路都在买"AI入场券"。从2015年收购的FPGA公司Altera,到后来的Nervana,再到2017年为押注无人车买下的Mobileye,都是英特尔下的重注。

在英特尔负责人工智能产品的Naveen Rao在接受The Information采访时说,英特尔崛起的PC时代"是一个非常简单的世界。而从今以后,应用空间已经改变。我们现在在数据中心处理的,是IoT、自动驾驶等各种各样的事情"。

不止买买买,英特尔内部也一直在为AI做准备。据说,英特尔内部的每个事业部,都曾经有过自己的AI项目。

这种状况到2016年英特尔收购Nervana才有了改变,Nervana CEO Rao随着公司加入这家巨头,负责一个专门的人工智能产品组(Artificial Intelligence Products Group)。

在Rao看来,英特尔这20个月以来最大的转变,就是全心全意地拥抱了加速芯片,接受了更广泛的芯片类型。

换句话说,就是不再一心专注于改进通用处理器CPU,也开始考虑专用于特定计算任务的加速芯片,以及用于自动驾驶等专门场景的芯片。

英特尔开发AI芯片的进度依然令人担忧。2016年收购Nervana后,本来说要2017年上半年发布的神经网络处理器10月才推出了"早期版本",之后就再也没有了消息。

而AI芯片这个领域,不仅创业公司林立,各大互联网公司也已经全部杀了进来。

欢迎大家关注我们的专栏:量子位 - 知乎专栏

诚挚招聘

量子位正在招募编辑/记者,工作地点在北京中关村。期待有才气、有热情的同学加入我们!相关细节,请在量子位公众号(QbitAI)对话界面,回复"招聘"两个字。

量子位 QbitAI · 头条号签约作者

վ'ᴗ' ի 追踪AI技术和产品新动态



via 量子位 - 知乎专栏 https://ift.tt/2Kw3y9A
RSS Feed

RSS5

IFTTT

全球AI芯片企业排行:英伟达第1,华为第12(七家中国公司入围Top24)

允中 夏乙 发自 凹非寺量子位 出品 | 公众号 QbitAI

AI芯片哪家强?

在调查研究了全球100多家企业后,市场研究和咨询公司Compass Intelligence发布了2018年度全球AI芯片公司排行榜。

在这份榜单上,英伟达排名第一。

这份报告也特别提到了英伟达丰富的产品线,包括用于数据中心的Volta GPU架构,自动驾驶平台NVIDIA DRIVE PX,DGX-1等等。

英特尔、IBM、Google、苹果、AMD、ARM、高通、三星、恩智浦等公司位列2-10名。

在Top24的榜单排行中,共有七家中国公司入围。

  • 华为(海思)位列这份榜单的第12位。
  • 联发科(MediaTek)排名第14位。
  • Imagination排名第15位。
  • 瑞芯微(Rockchip)排名第20位。
  • 芯原(Verisilcon)排名第21位。
  • 寒武纪(Cambricon)排名第23位。
  • 地平线(Horizon)排名第24位。

据介绍,这份一线AI芯片组排行榜包括提供AI芯片组软硬件的公司。

所谓AI芯片组产品包括:中央处理器(CPU)、图形处理器(GPU)、神经网络处理器(NNP)、专用集成电路(ASIC)、现场可编程门阵列(FPGA)、精简指令集(RISC)处理器、加速器等等。

以及针对边缘处理和设备的芯片组,云计算使用的服务器,针对机器视觉和自动驾驶平台的设备等。还包括AI的计算框架、训练平台等。

中国芯片军团

这七家入围的中国公司,有些公众很熟悉,有些相对低调。

我们先介绍几家相对低调一些的公司。

其中,Imagination原本是一家英国芯片企业,曾经是苹果公司的GPU供应商。去年9月,中国资本Canyon Bridge出资5.5亿英镑将其收购。

目前,Imagination公司CEO已由李力游出任,李力游此前曾任展讯的董事长,后展讯被紫光收购,李力游出任紫光集团联席总裁。

2017年9月,Imagination推出了面向人工智能应用的硬件IP产品PowerVR Series2NX NNA 神经网络加速器。

瑞芯微(Rockchip)成立于2001年,主要为智能手机、平板电脑、通讯平板,电视机顶盒、车载导航、IoT物联网等产品提供芯片解决方案。

据介绍,瑞芯微是一家做ARM应用处理器的公司,靠复读机和MP3/MP4芯片起家。今年一月,瑞芯微发布首款AI处理器RK3399Pro,NPU运算性能达2.4TOPs。

去年7月,瑞芯微创业板IPO未能过会,一度引发关注。

芯原(Verisilcon)也成立于2001年,自身定位是一家芯片设计平台即服务(Silicon Platform as a Service,SiPaaS)提供商。

去年5月,芯原推出一款面向计算机视觉和人工智能应用的处理器VIP8000,可以直接导入由Caffe和TensorFlow等框架生成的神经网络。恩智浦的旗舰应用处理器i.MX 8上也采用了芯原的视觉图形处理器。

2015年,芯原还收购了美国嵌入式图形处理器(GPU)设计商图芯技术(Vivante)。

芯原创始人戴伟民曾出任美国Celestry公司(2002年被Cadence并购)董事长兼CTO。

而华为、联发科、地平线这些公司,大家相对比较熟悉,或者量子位也有过不少报道,在这里不多赘言。

特别提一下,昨天寒武纪发布了最新的产品。

这也是两款早已经在路线图上公布过的产品。首先是寒武纪1M,这家公司的第三代机器学习专用芯片。1M使用TSMC 7nm工艺生产,其8位运算效能比达5Tops/watt(每瓦 5 万亿次运算)。

另外就是云端AI芯片MLU 100。这款芯片采用寒武纪MLUv01架构和TSMC 16nm工艺。在平衡模式(主频 1Ghz)和高性能模式(1.3GHz)主频下,其等效理论峰值速度则分别可以达到128万亿次定点运算/166.4万亿次定点运算,对应功耗为80w/110w。

"英特尔必须改"

最后,讲一个国外的芯片巨头。

开头的榜单上,英特尔携收购来的Mobileye、Nervana、Movidius,排在第二名。

"仅次于英伟达",对这家称霸芯片业数十年的巨头来说,可算不上什么值得夸耀的成就。

站在AI时代路口,英特尔其实已经很努力了。

这家巨头在数据中心芯片市场占据着90%的份额,这几年,数据中心所处理的计算任务随着人工智能兴起而逐渐改变,英伟达成了时代宠儿,英特尔深感地位岌岌可危。

于是,英特尔一路都在买"AI入场券"。从2015年收购的FPGA公司Altera,到后来的Nervana,再到2017年为押注无人车买下的Mobileye,都是英特尔下的重注。

在英特尔负责人工智能产品的Naveen Rao在接受The Information采访时说,英特尔崛起的PC时代"是一个非常简单的世界。而从今以后,应用空间已经改变。我们现在在数据中心处理的,是IoT、自动驾驶等各种各样的事情"。

不止买买买,英特尔内部也一直在为AI做准备。据说,英特尔内部的每个事业部,都曾经有过自己的AI项目。

这种状况到2016年英特尔收购Nervana才有了改变,Nervana CEO Rao随着公司加入这家巨头,负责一个专门的人工智能产品组(Artificial Intelligence Products Group)。

在Rao看来,英特尔这20个月以来最大的转变,就是全心全意地拥抱了加速芯片,接受了更广泛的芯片类型。

换句话说,就是不再一心专注于改进通用处理器CPU,也开始考虑专用于特定计算任务的加速芯片,以及用于自动驾驶等专门场景的芯片。

英特尔开发AI芯片的进度依然令人担忧。2016年收购Nervana后,本来说要2017年上半年发布的神经网络处理器10月才推出了"早期版本",之后就再也没有了消息。

而AI芯片这个领域,不仅创业公司林立,各大互联网公司也已经全部杀了进来。

欢迎大家关注我们的专栏:量子位 - 知乎专栏

诚挚招聘

量子位正在招募编辑/记者,工作地点在北京中关村。期待有才气、有热情的同学加入我们!相关细节,请在量子位公众号(QbitAI)对话界面,回复"招聘"两个字。

量子位 QbitAI · 头条号签约作者

վ'ᴗ' ի 追踪AI技术和产品新动态



via 量子位 - 知乎专栏 https://ift.tt/2Kw3y9A
RSS Feed

RSS5

IFTTT

LangChain 彻底重写:从开源副业到独角兽,一次“核心迁移”干到 12.5 亿估值 -InfoQ 每周精要No.899期

「每周精要」 NO. 899 2025/10/25 头条 HEADLINE LangChain 彻底重写:从开源副业到独角兽,一次"核心迁移"干到 12.5 亿估值 精选 SELECTED 1000 行代码手搓 OpenAI gpt-oss 推理引...