去噪自编码器_DeepLearning 0.1 documentation中文翻译

本文介绍了去噪自编码器的基本原理与实现方法,详细解释了其如何通过重构受损输入来学习鲁棒性特征,避免学习恒等函数,并提供了完整的程序实现及训练过程。

DeepLearning 0.1 documentation中文翻译: Denoising Autoencoders (DA)_去噪自编码器

原文网址: http://deeplearning.net/tutorial/dA.html


Note
这部分假设读者已经阅读过Classifying MNIST digits using Logistic RegressionMultilayer Perceptron. 此外, 本节会使用到新的Theano函数和概念: T.tanh, shared variables, basic arithmetic ops, T.grad, Random numbers, floatX. 如果你打算在GPU上运行,也要阅读GPU.
Note
代码可以在此下载:code

去噪自编码器是经典自编码器的扩展, 并且在文献[Vincent08]中, 它被作为深度网络的组件引入. 我们将下面简单介绍一下自编码器(Autoencoders).

自编码器(Autoencoders)

自编码器的概述参见文献[Bengio09]的4.6部分. 一个自编码器以 x[0,1]d 作为输入, 并且首先将输入通过一个确定性的映射, 映射为 (with an encoder)隐层表示 y[0,1]d , e.g.:

y=s(Wx+b)

其中, s 是一个非线性映射, 如sigmoid. 隐式表示 y, 或者叫编码, 紧接着被映射回来(with a decoder), 形成重构(Reconstruction) z , 它与 x 有同样的形状大小. 这个映射也是通过类似编码映射的变换, e.g.:

z=s(Wy+b)

(这里, 撇符号不表示矩阵转置.) z 应当被看作给定编码 y 时, 对 x 的预测. 逆映射的权重矩阵 W 可以选择约束成正向映射的转置: W=WT . 这被称为捆绑权重. 如果不使用捆绑的权重, 那么对这个模型的参数 (即 W,b,b , 和 W (不使用捆绑权重)) 进行优化, 以使得平均重构误差最小.

重构误差可以从很多方面来衡量, 这取决于在给定编码时,对输入的适当的分布假设. 可以用传统的均方误差 L(xz)=||xz||2 . 如果输入被解释为位向量或位概率(bit probabilities)向量, 那么可以使用输入与重构的交叉熵(cross-entropy)来衡量:

LH(x,z)=k=1d[xklogzk+(1xk)log(1zk)]

我们希望编码 y 是一个分布式表示, 它可以抓住数据变化主要因素的方向. 这类似于向主成分方向投影的方式, 都将获得数据变化的主要因素. 实际上, 如果有一个线性隐藏层(编码), 并且使用均方误差准则来训练网络, 那么, k 个隐藏层单元就是学习如何将输入投影到由数据的前k个主成分张成的子空间里(in the span of the first k principal components of the data). 如果隐藏层是非线性的, 自编码器与PCA表现不同, 它具有捕获数据分布多模态(multi-modal)的能力. 因而, 当我们构建深度自编码器[Hinton06]时, 我们会堆叠多个编码器(stacking multiple encoders, 及其对应的解码器), 此时, 就不能以PCA的观点来看待.

因为y x 的有损压缩, 所以对于所有的 x , y 不能很好(small-loss)的压缩 x . 优化自编码器模型, 使 y 成为训练样例的良好压缩, 并希望自编码器对其它的输入也能较好的压缩, 但不是说对任意的输入都有良好的压缩. 那么一个自编码器推广的意义就是: 一个自编码器在与训练样例(training examples)有相同分布的测试样例(test examples)上的重构误差低, 然而, 一般说来, 对在从输入空间随机抽取的样例上的重构误差很高.

我们想使用Theano, 以类的形式实现一个自编码器, 它最终会被用于构造堆叠式自编码器(也叫堆栈式stacked autoencoder). 第一步是, 为自编码器的参数 W,b b 创建共享变量(shared variables). (由于本教程中使用捆绑权重, W 取为 WT ):

class dA(object):
    """Denoising Auto-Encoder class (dA)

    A denoising autoencoders tries to reconstruct the input from a corrupted
    version of it by projecting it first in a latent space and reprojecting
    it afterwards back in the input space. Please refer to Vincent et al.,2008
    for more details. If x is the input then equation (1) computes a partially
    destroyed version of x by means of a stochastic mapping q_D. Equation (2)
    computes the projection of the input into the latent space. Equation (3)
    computes the reconstruction of the input, while equation (4) computes the
    reconstruction error.

    .. math::

        \tilde{x} ~ q_D(\tilde{x}|x)                                     (1)

        y = s(W \tilde{x} + b)                                           (2)

        x = s(W' y  + b')                                                (3)

        L(x,z) = -sum_{k=1}^d [x_k \log z_k + (1-x_k) \log( 1-z_k)]      (4)

    """

    def __init__(
        self,
        numpy_rng,
        theano_rng=None,
        input=None,
        n_visible=784,
        n_hidden=500,
        W=None,
        bhid=None,
        bvis=None
    ):
        """
        Initialize the dA class by specifying the number of visible units (the
        dimension d of the input ), the number of hidden units ( the dimension
        d' of the latent or hidden space ) and the corruption level. The
        constructor also receives symbolic variables for the input, weights and
        bias. Such a symbolic variables are useful when, for example the input
        is the result of some computations, or when weights are shared between
        the dA and an MLP layer. When dealing with SdAs this always happens,
        the dA on layer 2 gets as input the output of the dA on layer 1,
        and the weights of the dA are used in the second stage of training
        to construct an MLP.

        :type numpy_rng: numpy.random.RandomState
        :param numpy_rng: number random generator used to generate weights

        :type theano_rng: theano.tensor.shared_randomstreams.RandomStreams
        :param theano_rng: Theano random generator; if None is given one is
                     generated based on a seed drawn from `rng`

        :type input: theano.tensor.TensorType
        :param input: a symbolic description of the input or None for
                      standalone dA

        :type n_visible: int
        :param n_visible: number of visible units

        :type n_hidden: int
        :param n_hidden:  number of hidden units

        :type W: theano.tensor.TensorType
        :param W: Theano variable pointing to a set of weights that should be
                  shared belong the dA and another architecture; if dA should
                  be standalone set this to None

        :type bhid: theano.tensor.TensorType
        :param bhid: Theano variable pointing to a set of biases values (for
                     hidden units) that should be shared belong dA and another
                     architecture; if dA should be standalone set this to None

        :type bvis: theano.tensor.TensorType
        :param bvis: Theano variable pointing to a set of biases values (for
                     visible units) that should be shared belong dA and another
                     architecture; if dA should be standalone set this to None


        """
        self.n_visible = n_visible
        self.n_hidden = n_hidden

        # create a Theano random generator that gives symbolic random values
        if not theano_rng:
            theano_rng = RandomStreams(numpy_rng.randint(2 ** 30))

        # note : W' was written as `W_prime` and b' as `b_prime`
        if not W:
            # W is initialized with `initial_W` which is uniformely sampled
            # from -4*sqrt(6./(n_visible+n_hidden)) and
            # 4*sqrt(6./(n_hidden+n_visible))the output of uniform if
            # converted using asarray to dtype
            # theano.config.floatX so that the code is runable on GPU
            initial_W = numpy.asarray(
                numpy_rng.uniform(
                    low=-4 * numpy.sqrt(6. / (n_hidden + n_visible)),
                    high=4 * numpy.sqrt(6. / (n_hidden + n_visible)),
                    size=(n_visible, n_hidden)
                ),
                dtype=theano.config.floatX
            )
            W = theano.shared(value=initial_W, name='W', borrow=True)

        if not bvis:
            bvis = theano.shared(
                value=numpy.zeros(
                    n_visible,
                    dtype=theano.config.floatX
                ),
                borrow=True
            )

        if not bhid:
            bhid = theano.shared(
                value=numpy.zeros(
                    n_hidden,
                    dtype=theano.config.floatX
                ),
                name='b',
                borrow=True
            )

        self.W = W
        # b corresponds to the bias of the hidden
        self.b = bhid
        # b_prime corresponds to the bias of the visible
        self.b_prime = bvis
        # tied weights, therefore W_prime is W transpose
        self.W_prime = self.W.T
        self.theano_rng = theano_rng
        # if no input is given, generate a variable representing the input
        if input is None:
            # we use a matrix because we expect a minibatch of several
            # examples, each example being a row
            self.x = T.dmatrix(name='input')
        else:
            self.x = input

        self.params = [self.W, self.b, self.b_prime]

请注意, 我们传递给自编码器的输入参数是符号形式的, 这样, 我们可以连接自编码器的各层, 来形成深度网络: 第 k 层的符号形的输出, 将作为第k+1层符号形的输入.

现在, 我们可以计算隐层表示和信号的重构:

def get_hidden_values(self, input):
        """ Computes the values of the hidden layer """
        return T.nnet.sigmoid(T.dot(input, self.W) + self.b)
def get_reconstructed_input(self, hidden):
        """Computes the reconstructed input given the values of the
        hidden layer

        """
        return T.nnet.sigmoid(T.dot(hidden, self.W_prime) + self.b_prime)

并且, 使用这些函数, 可以计算代价和一次随机梯度下降步骤的更新:

def get_cost_updates(self, corruption_level, learning_rate):
        """ This function computes the cost and the updates for one trainng
        step of the dA """

        tilde_x = self.get_corrupted_input(self.x, corruption_level)
        y = self.get_hidden_values(tilde_x)
        z = self.get_reconstructed_input(y)
        # note : we sum over the size of a datapoint; if we are using
        #        minibatches, L will be a vector, with one entry per
        #        example in minibatch
        L = - T.sum(self.x * T.log(z) + (1 - self.x) * T.log(1 - z), axis=1)
        # note : L is now a vector, where each element is the
        #        cross-entropy cost of the reconstruction of the
        #        corresponding example of the minibatch. We need to
        #        compute the average of all these to get the cost of
        #        the minibatch
        cost = T.mean(L)

        # compute the gradients of the cost of the `dA` with respect
        # to its parameters
        gparams = T.grad(cost, self.params)
        # generate the list of updates
        updates = [
            (param, param - learning_rate * gparam)
            for param, gparam in zip(self.params, gparams)
        ]

        return (cost, updates)

现在, 我们可以定义用于迭代更新参数 W, bb_prime 的函数, 以使重构代价接近最小化:

da = dA(
        numpy_rng=rng,
        theano_rng=theano_rng,
        input=x,
        n_visible=28 * 28,
        n_hidden=500
    )

    cost, updates = da.get_cost_updates(
        corruption_level=0.,
        learning_rate=learning_rate
    )

    train_da = theano.function(
        [index],
        cost,
        updates=updates,
        givens={
            x: train_set_x[index * batch_size: (index + 1) * batch_size]
        }
    )

如果除了重建误差最小化外没有其它约束, 我们可以预料到的是: 一个 n 输入和n维(或更大)的自编码器, 将学习恒等函数, 几乎将输入映射成它自己的副本. 这样的一个自编码器, 不能区分来自其它输入配置的测试样例.

令人惊讶的是, 文献[ bengio07 ]的实验表明: 实际上, 隐藏层单元远多于输入(称为过完备, overcomplete)的非线性自编码器, 当采用随机梯度下降法训练时, 会产生有用表示(useful representations). ( 在这里, “有用”是指一个网络以编码作为输入, 分类错误率较低. )

一个简单的解释是, 早期停止迭代的随机梯度下降法与参数的 L2 正则化类似. 为了实现连续输入的完美重构, 一个单隐层非线性的自动编码器( 就像上面代码中的 ), 在第一( 编码 )层, 需要非常小的权重, 而将隐层单元的非线性转到线性性, 在第二( 解码 )层, 需要非常大的权重. 对于二值输入, 要完全最小化重构误差, 也需要非常大的权重. 隐式或显式的正则化, 使得得到非常大的权重解很困难, 优化算法仅仅对那些与训练集相似的样例有好的编码效果, 这也是我们想要的. 这意味着, 表示利用了训练集中的统计规律, 而不是仅仅学习复制输入.

也有其它方法, 来阻止隐藏层单元多于输入的一个自编码器, 学习恒等函数, 并让它在隐层学到有用的东西. 方法之一是加入稀疏性( 迫使许多隐藏层单元为零或接近零 )限制, 稀疏性已经广泛地被成功应用[ Ranzato07 ] [ Lee08 ]. 另一种方法是从在从输入到重构的转换中加入随机性. 这种技术被被用在受限波尔兹曼机( Restricted Boltzmann Machines (RBM), 在后面的受限波尔兹曼机( RBM )中会讨论 )中, 以及下面要讨论的去噪自动编码器中.

去噪自编码器(Denoising Autoencoders)

去噪自编码器背后的思想很简单. 为了迫使隐藏层单元发现更多鲁棒性好的特征, 以及阻止它学习恒等函数, 我们拿受损的输入来训练自编码器重构输入.

降噪自动编码器是自动编码器的随机版. 直观地说, 一个降噪自动编码器做两件事情: 试图对输入编码( 保存输入的信息 ), 和试图消除随机应用于自动编码器输入的损坏处理的影响. 后者则只能通过输入之间的统计相关性实现. 降噪自动编码器可以从不同的角度理解( 流形学习的角度, 随机算子的角度, 自底而上的信息理论的角度, 和自顶而下的生成模型的角度 ), 这些都在文献[ vincent08 ]中有解释. 关于自动编码器概述, 参见文献[ bengio09 ]的7.2节.

在文献[ vincent08 ]中, 随机损坏处理随机地将一些输入( 多达一半 )置零. 因此, 对于随机选择的丢失了特征的子集, 降噪自动编码器尝试根据未损坏( 即未丢失 )的值来预测损坏( 即丢失 )的值. 注意, 如何能够从剩余集合中预测任意变量的子集, 是完全获得一个集合的变量间的联合分布的充分条件( 这就是吉布斯采样(Gibbs sampling)的原理 ).

将自编码器类转换成去噪自编码器类, 我们需要做的是给输入增加一个随机损坏(stochastic corruption)操作. 输入可以以多种方式破坏, 但在本教程中, 我们将坚持使用原始的破坏机制, 即随机地将输入的某些项置零以掩盖这些输入. 下面的代码就是这样做的:

from theano.tensor.shared_randomstreams import RandomStreams

def get_corrupted_input(self, input, corruption_level):
      """ This function keeps ``1-corruption_level`` entries of the inputs the same
      and zero-out randomly selected subset of size ``coruption_level``
      Note : first argument of theano.rng.binomial is the shape(size) of
             random numbers that it should produce
             second argument is the number of trials
             third argument is the probability of success of any trial

              this will produce an array of 0s and 1s where 1 has a probability of
              1 - ``corruption_level`` and 0 with ``corruption_level``
      """
      return  self.theano_rng.binomial(size=input.shape, n=1, p=1 - corruption_level) * input

在堆叠式自编码器类(Stacked Autoencoders) 中, dA 类的权重, 必须与相应的 sigmoid 层的权重共享. 为此, dA 的构造函数也获得指向共享参数的Theano变量. 如果这些参数为空的话, 将会新建一个.

最终的去噪自编码器类变成这样:

class dA(object):
   """Denoising Auto-Encoder class (dA)

   A denoising autoencoders tries to reconstruct the input from a corrupted
   version of it by projecting it first in a latent space and reprojecting
   it afterwards back in the input space. Please refer to Vincent et al.,2008
   for more details. If x is the input then equation (1) computes a partially
   destroyed version of x by means of a stochastic mapping q_D. Equation (2)
   computes the projection of the input into the latent space. Equation (3)
   computes the reconstruction of the input, while equation (4) computes the
   reconstruction error.

   .. math::

       \tilde{x} ~ q_D(\tilde{x}|x)                                     (1)

       y = s(W \tilde{x} + b)                                           (2)

       x = s(W' y  + b')                                                (3)

       L(x,z) = -sum_{k=1}^d [x_k \log z_k + (1-x_k) \log( 1-z_k)]      (4)

   """

   def __init__(self, numpy_rng, theano_rng=None, input=None, n_visible=784, n_hidden=500,
              W=None, bhid=None, bvis=None):
       """
       Initialize the dA class by specifying the number of visible units (the
       dimension d of the input ), the number of hidden units ( the dimension
       d' of the latent or hidden space ) and the corruption level. The
       constructor also receives symbolic variables for the input, weights and
       bias. Such a symbolic variables are useful when, for example the input is
       the result of some computations, or when weights are shared between the
       dA and an MLP layer. When dealing with SdAs this always happens,
       the dA on layer 2 gets as input the output of the dA on layer 1,
       and the weights of the dA are used in the second stage of training
       to construct an MLP.

       :type numpy_rng: numpy.random.RandomState
       :param numpy_rng: number random generator used to generate weights

       :type theano_rng: theano.tensor.shared_randomstreams.RandomStreams
       :param theano_rng: Theano random generator; if None is given one is generated
                    based on a seed drawn from `rng`

       :type input: theano.tensor.TensorType
       :paran input: a symbolic description of the input or None for standalone
                     dA

       :type n_visible: int
       :param n_visible: number of visible units

       :type n_hidden: int
       :param n_hidden:  number of hidden units

       :type W: theano.tensor.TensorType
       :param W: Theano variable pointing to a set of weights that should be
                 shared belong the dA and another architecture; if dA should
                 be standalone set this to None

       :type bhid: theano.tensor.TensorType
       :param bhid: Theano variable pointing to a set of biases values (for
                    hidden units) that should be shared belong dA and another
                    architecture; if dA should be standalone set this to None

       :type bvis: theano.tensor.TensorType
       :param bvis: Theano variable pointing to a set of biases values (for
                    visible units) that should be shared belong dA and another
                    architecture; if dA should be standalone set this to None


       """
       self.n_visible = n_visible
       self.n_hidden = n_hidden

       # create a Theano random generator that gives symbolic random values
       if not theano_rng :
           theano_rng = RandomStreams(rng.randint(2 ** 30))

       # note : W' was written as `W_prime` and b' as `b_prime`
       if not W:
           # W is initialized with `initial_W` which is uniformely sampled
           # from -4.*sqrt(6./(n_visible+n_hidden)) and 4.*sqrt(6./(n_hidden+n_visible))
           # the output of uniform if converted using asarray to dtype
           # theano.config.floatX so that the code is runable on GPU
           initial_W = numpy.asarray(numpy_rng.uniform(
                     low=-4 * numpy.sqrt(6. / (n_hidden + n_visible)),
                     high=4 * numpy.sqrt(6. / (n_hidden + n_visible)),
                     size=(n_visible, n_hidden)), dtype=theano.config.floatX)
           W = theano.shared(value=initial_W, name='W')

       if not bvis:
           bvis = theano.shared(value = numpy.zeros(n_visible,
                                        dtype=theano.config.floatX), name='bvis')

       if not bhid:
           bhid = theano.shared(value=numpy.zeros(n_hidden,
                                             dtype=theano.config.floatX), name='bhid')

       self.W = W
       # b corresponds to the bias of the hidden
       self.b = bhid
       # b_prime corresponds to the bias of the visible
       self.b_prime = bvis
       # tied weights, therefore W_prime is W transpose
       self.W_prime = self.W.T
       self.theano_rng = theano_rng
       # if no input is given, generate a variable representing the input
       if input == None:
           # we use a matrix because we expect a minibatch of several examples,
           # each example being a row
           self.x = T.dmatrix(name='input')
       else:
           self.x = input

       self.params = [self.W, self.b, self.b_prime]

   def get_corrupted_input(self, input, corruption_level):
       """ This function keeps ``1-corruption_level`` entries of the inputs the same
       and zero-out randomly selected subset of size ``coruption_level``
       Note : first argument of theano.rng.binomial is the shape(size) of
              random numbers that it should produce
              second argument is the number of trials
              third argument is the probability of success of any trial

               this will produce an array of 0s and 1s where 1 has a probability of
               1 - ``corruption_level`` and 0 with ``corruption_level``
       """
       return  self.theano_rng.binomial(size=input.shape, n=1, p=1 - corruption_level) * input


   def get_hidden_values(self, input):
       """ Computes the values of the hidden layer """
       return T.nnet.sigmoid(T.dot(input, self.W) + self.b)

   def get_reconstructed_input(self, hidden ):
       """ Computes the reconstructed input given the values of the hidden layer """
       return  T.nnet.sigmoid(T.dot(hidden, self.W_prime) + self.b_prime)

   def get_cost_updates(self, corruption_level, learning_rate):
       """ This function computes the cost and the updates for one trainng
       step of the dA """

       tilde_x = self.get_corrupted_input(self.x, corruption_level)
       y = self.get_hidden_values( tilde_x)
       z = self.get_reconstructed_input(y)
       # note : we sum over the size of a datapoint; if we are using minibatches,
       #        L will  be a vector, with one entry per example in minibatch
       L = -T.sum(self.x * T.log(z) + (1 - self.x) * T.log(1 - z), axis=1 )
       # note : L is now a vector, where each element is the cross-entropy cost
       #        of the reconstruction of the corresponding example of the
       #        minibatch. We need to compute the average of all these to get
       #        the cost of the minibatch
       cost = T.mean(L)

       # compute the gradients of the cost of the `dA` with respect
       # to its parameters
       gparams = T.grad(cost, self.params)
       # generate the list of updates
       updates = []
       for param, gparam in zip(self.params, gparams):
           updates.append((param, param - learning_rate * gparam))

       return (cost, updates)

整合程序(Putting it All Together)

现在很容易构造一个dA类实例, 并训练它.

# allocate symbolic variables for the data
index = T.lscalar()  # index to a [mini]batch
x = T.matrix('x')  # the data is presented as rasterized images

######################
# BUILDING THE MODEL #
######################

rng = numpy.random.RandomState(123)
theano_rng = RandomStreams(rng.randint(2 ** 30))

da = dA(numpy_rng=rng, theano_rng=theano_rng, input=x,
        n_visible=28 * 28, n_hidden=500)

cost, updates = da.get_cost_updates(corruption_level=0.2,
                            learning_rate=learning_rate)


train_da = theano.function([index], cost, updates=updates,
     givens = {x: train_set_x[index * batch_size: (index + 1) * batch_size]})

start_time = time.clock()

############
# TRAINING #
############

# go through training epochs
for epoch in xrange(training_epochs):
    # go through trainng set
    c = []
    for batch_index in xrange(n_train_batches):
        c.append(train_da(batch_index))

    print 'Training epoch %d, cost ' % epoch, numpy.mean(c)

end_time = time.clock

training_time = (end_time - start_time)

print ('Training took %f minutes' % (pretraining_time / 60.))

为了看到网络到底学习到了什么, 我们准备绘制出这个滤波器(由权重矩阵定义的). 然而, 请记住, 这并不能代表所有, 因为我们忽略了偏置, 并将权重归一化到0和1之间.

为了绘制滤波器, 我们需要利用 tile_raster_images 函数(see Plotting Samples and Filters) , 因此我们希望读者自己熟悉它. 在Python 图像库( Python Image Library ) 的帮助下, 下面的这些代码行将把滤波器保存成图像:

image = Image.fromarray(tile_raster_images(X=da.W.get_value(borrow=True).T,
             img_shape=(28, 28), tile_shape=(10, 10),
             tile_spacing=(1, 1)))
image.save('filters_corruption_30.png')

运行代码(Running the Code)

运行代码 :

python dA.py

未加任何噪声, 得到的滤波器结果:
The resulted filters when we do not use any noise are :

加入30%的噪声时的滤波器结果:
The filters for 30 percent noise :




DeepLearning 0.1 documentation中文翻译

Duplicate class org.MediaPlayer.PlayM4.PlayerCallBack$ PlayerEncryptTypeCB found in the following modules: jetified-ERTC_Android_SDK_1.5.0.1-runtime (ERTC_Android_SDK_1.5.0.1.aar), jetified-ezviz-sdk-4.9.1.1-runtime (com.ezviz.sdk:ezviz-sdk:4.9.1.1) and jetified-ezviz-sdk-5.20-runtime (io.github.ezviz-open:ezviz-sdk:5.20) Duplicate class org.MediaPlayer.PlayM4.PlayerCallBack$ PlayerFECDisplayCB found in the following modules: jetified-ERTC_Android_SDK_1.5.0.1-runtime (ERTC_Android_SDK_1.5.0.1.aar), jetified-ezviz-sdk-4.9.1.1-runtime (com.ezviz.sdk:ezviz-sdk:4.9.1.1) and jetified-ezviz-sdk-5.20-runtime (io.github.ezviz-open:ezviz-sdk:5.20) Duplicate class org.MediaPlayer.PlayM4.PlayerCallBack$ PlayerFileRefCB found in the following modules: jetified-ERTC_Android_SDK_1.5.0.1-runtime (ERTC_Android_SDK_1.5.0.1.aar), jetified-ezviz-sdk-4.9.1.1-runtime (com.ezviz.sdk:ezviz-sdk:4.9.1.1) and jetified-ezviz-sdk-5.20-runtime (io.github.ezviz-open:ezviz-sdk:5.20) Duplicate class org.MediaPlayer.PlayM4.PlayerCallBack$ PlayerHSDetectCB found in the following modules: jetified-ERTC_Android_SDK_1.5.0.1-runtime (ERTC_Android_SDK_1.5.0.1.aar), jetified-ezviz-sdk-4.9.1.1-runtime (com.ezviz.sdk:ezviz-sdk:4.9.1.1) and jetified-ezviz-sdk-5.20-runtime (io.github.ezviz-open:ezviz-sdk:5.20) Duplicate class org.MediaPlayer.PlayM4.PlayerCallBack$ PlayerIVSDrawFunCB found in the following modules: jetified-ERTC_Android_SDK_1.5.0.1-runtime (ERTC_Android_SDK_1.5.0.1.aar), jetified-ezviz-sdk-4.9.1.1-runtime (com.ezviz.sdk:ezviz-sdk:4.9.1.1) and jetified-ezviz-sdk-5.20-runtime (io.github.ezviz-open:ezviz-sdk:5.20) Duplicate class org.MediaPlayer.PlayM4.PlayerCallBack$ PlayerLogCB found in modules jetified-ERTC_Android_SDK_1.5.0.1-runtime (ERTC_Android_SDK_1.5.0.1.aar) and jetified-ezviz-sdk-5.20-runtime (io.github.ezviz-open:ezviz-sdk:5.20) Duplicate class org.MediaPlayer.PlayM4.PlayerCallBack$ PlayerPlayEndCB found in the following modules: jetified-ERTC_Android_SDK_1.5.0.1-runtime (ERTC_Android_SDK_1.5.0.1.aar), jetified-ezviz-sdk-4.9.1.1-runtime (com.ezviz.sdk:ezviz-sdk:4.9.1.1) and jetified-ezviz-sdk-5.20-runtime (io.github.ezviz-open:ezviz-sdk:5.20) Duplicate class org.MediaPlayer.PlayM4.PlayerCallBack$ PlayerPreRecordCB found in the following modules: jetified-ERTC_Android_SDK_1.5.0.1-runtime (ERTC_Android_SDK_1.5.0.1.aar), jetified-ezviz-sdk-4.9.1.1-runtime (com.ezviz.sdk:ezviz-sdk:4.9.1.1) and jetified-ezviz-sdk-5.20-runtime (io.github.ezviz-open:ezviz-sdk:5.20) Duplicate class org.MediaPlayer.PlayM4.PlayerCallBack$ PlayerPreRecordCBEx found in the following modules: jetified-ERTC_Android_SDK_1.5.0.1-runtime (ERTC_Android_SDK_1.5.0.1.aar), jetified-ezviz-sdk-4.9.1.1-runtime (com.ezviz.sdk:ezviz-sdk:4.9.1.1) and jetified-ezviz-sdk-5.20-runtime (io.github.ezviz-open:ezviz-sdk:5.20) Duplicate class org.MediaPlayer.PlayM4.PlayerCallBack$ PlayerRunTimeInfoCB found in the following modules: jetified-ERTC_Android_SDK_1.5.0.1-runtime (ERTC_Android_SDK_1.5.0.1.aar), jetified-ezviz-sdk-4.9.1.1-runtime (com.ezviz.sdk:ezviz-sdk:4.9.1.1) and jetified-ezviz-sdk-5.20-runtime (io.github.ezviz-open:ezviz-sdk:5.20) Duplicate class org.MediaPlayer.PlayM4.PlayerCallBack$ PlayerVideoFrameCB found in modules jetified-ERTC_Android_SDK_1.5.0.1-runtime (ERTC_Android_SDK_1.5.0.1.aar) and jetified-ezviz-sdk-5.20-runtime (io.github.ezviz-open:ezviz-sdk:5.20) Duplicate class org.MediaPlayer.PlayM4.PlayerCallBack$ RealTimeRenderFrameRateCB found in modules jetified-ERTC_Android_SDK_1.5.0.1-runtime (ERTC_Android_SDK_1.5.0.1.aar) and jetified-ezviz-sdk-5.20-runtime (io.github.ezviz-open:ezviz-sdk:5.20) Duplicate class org.MediaPlayer.PlayM4.SurfaceCallBack found in the following modules: jetified-ERTC_Android_SDK_1.5.0.1-runtime (ERTC_Android_SDK_1.5.0.1.aar), jetified-ezviz-sdk-4.9.1.1-runtime (com.ezviz.sdk:ezviz-sdk:4.9.1.1) and jetified-ezviz-sdk-5.20-runtime (io.github.ezviz-open:ezviz-sdk:5.20) Duplicate class org.MediaPlayer.PlayM4.TimeStruct found in the following modules: jetified-ERTC_Android_SDK_1.5.0.1-runtime (ERTC_Android_SDK_1.5.0.1.aar), jetified-PlayerSDK_hcnetsdk (PlayerSDK_hcnetsdk.jar), jetified-ezviz-sdk-4.9.1.1-runtime (com.ezviz.sdk:ezviz-sdk:4.9.1.1) and jetified-ezviz-sdk-5.20-runtime (io.github.ezviz-open:ezviz-sdk:5.20) Go to the documentation to learn how to Fix dependency resolution errors. 解决Android studio中renbuild project后的报错
06-23
<think>问题分析:你提供的错误日志显示,在Android项目中存在多个重复的类(Duplicateclass)。具体来说,这些类来自三个不同的库:1.ERTC_Android_SDK_1.5.0.1.aar2.com.ezviz.sdk:ezviz-sdk:4.9.1.13.io.github.ezviz-open:ezviz-sdk:5.20错误原因:这些库中包含了相同包名和类名的类,导致编译时冲突。例如,类`org.MediaPlayer.PlayM4.PlayerCallBack$PlayerEncryptTypeCB`在三个库中都存在。解决方案:通常,解决重复类问题有以下几种方法:1.排除重复的依赖:在Gradle中,使用`exclude`语句排除特定模块中的重复类或整个模块。2.统一库的版本:如果这些库是同一个库的不同版本,尝试统一使用一个版本。3.移除不必要的依赖:检查是否同时引入了多个功能重复的库,移除不必要的库。根据错误信息,我们注意到有两个不同版本的ezviz-sdk(4.9.1.1和5.20)以及一个ERTC_Android_SDK。首先需要确定这些库的功能是否重叠,以及项目实际需要哪个版本。假设我们决定保留最新版本的ezviz-sdk(5.20),并排除其他库中的重复类。但注意,ERTC_Android_SDK可能是一个独立的SDK,我们可能不能直接移除它。因此,我们可以尝试排除ERTC_Android_SDK中的重复包。步骤:1.在项目的build.gradle文件中,检查依赖项,找到引入这些库的地方。2.对ERTC_Android_SDK和旧版本的ezviz-sdk进行排除操作。例如,如果依赖项如下:```implementation'com.ezviz.sdk:ezviz-sdk:4.9.1.1'implementation'io.github.ezviz-open:ezviz-sdk:5.20'implementationfiles('libs/ERTC_Android_SDK_1.5.0.1.aar')```我们可以修改为:移除旧版本的ezviz-sdk(4.9.1.1),因为已经使用了5.20版本。对于ERTC_Android_SDK,我们可以排除重复的包。但是,由于错误中涉及多个类,排除每个类会很麻烦,而且可能还有未列出的重复类。因此,如果可能,最好联系SDK提供商,看是否有不包含这些重复类的新版本。如果必须使用ERTC_Android_SDK,我们可以尝试排除它里面的某些包。但是,由于错误信息中提到的类都在`org.MediaPlayer.PlayM4`包下,我们可以尝试排除整个包(但通常Gradle不允许排除特定包,只能排除模块或整个依赖)。另一种方法是,在依赖ERTC_Android_SDK时,排除与ezviz-sdk重复的类所在的包。但是,Gradle的排除是基于模块和组,而不是基于包名。所以,我们需要排除整个模块,或者使用更细粒度的方式。实际上,我们可以这样排除ERTC_Android_SDK中的重复类(但Gradle不支持按类排除,只支持按模块排除)。所以,如果ERTC_Android_SDK和ezviz-sdk是同一个公司的产品,可能应该只使用其中一个。考虑到错误信息中还有`PlayerSDK_hcnetsdk.jar`,它同样包含了重复的类`org.MediaPlayer.PlayM4.TimeStruct`,所以也需要处理。因此,建议的步骤:1.移除旧版本的ezviz-sdk(4.9.1.1),因为已经有一个更新的版本(5.20)。2.排除ERTC_Android_SDK中的`org.MediaPlayer.PlayM4`包(但无法直接排除包,所以可能需要考虑是否真的需要ERTC_Android_SDK,或者尝试用ezviz-sdk代替)。3.排除PlayerSDK_hcnetsdk.jar中的重复包?同样,无法直接排除包。实际上,更常见的做法是排除整个模块中的重复包,但Gradle并不支持。所以,我们需要考虑是否这些库是互斥的,或者能否只使用其中一个。如果项目确实需要同时使用这些库,并且它们有部分重复的类,那么可能需要通过自定义构建过程来合并类,但这很复杂。因此,最直接的方法是联系库的提供者,或者选择其中一个库。然而,在无法更换库的情况下,我们可以尝试以下Gradle配置,通过排除ERTC_Android_SDK中的某些模块(如果它由多个模块组成)来避免重复。但根据错误,ERTC_Android_SDK是一个aar,它可能是一个整体。另一种思路:使用`pickFirst`(在打包时选择第一个遇到的类)或者`exclude`(排除整个依赖)来处理。但是,在Android中,我们可以通过packagingOptions来设置pickFirst。不过,pickFirst适用于多个jar中包含相同的文件(例如相同的类文件),但这里的情况是多个依赖中存在相同的类(不同的jar/aar中有相同的全限定类名)。在AndroidGradle插件中,可以使用packagingOptions来指定在遇到重复类时如何处理,例如:```android{packagingOptions{pickFirst'org/MediaPlayer/PlayM4/PlayerCallBack$PlayerEncryptTypeCB.class'//为每一个重复的类都添加一个pickFirst}}```但是,这样需要为每一个重复的类都写一行,而且如果重复的类很多,会非常麻烦。另外,这种方法只能解决多个依赖中有完全相同类文件的问题,但并不能保证兼容性,因为不同库中的相同类可能内容不同。因此,最可靠的方法还是移除或排除其中一个依赖。根据错误信息,重复的类主要来自三个库:ERTC_Android_SDK、ezviz-sdk-4.9.1.1和ezviz-sdk-5.20。我们首先尝试移除ezviz-sdk-4.9.1.1,因为已经有了更新的5.20。然后,对于ERTC_Android_SDK和ezviz-sdk-5.20之间的重复,我们需要决定保留哪一个。如果ERTC_Android_SDK是必须的,而ezviz-sdk也是必须的,那么我们可以尝试排除ezviz-sdk中与ERTC_Android_SDK重复的包(如果ezviz-sdk允许这样排除的话)。在Gradle中,排除依赖可以这样写:```implementation('io.github.ezviz-open:ezviz-sdk:5.20'){excludegroup:'org.MediaPlayer',module:'PlayM4'}```但是,通常group是库的组名,而不是包名。而且,这里的`org.MediaPlayer`并不是一个group,而是包名。所以,上述方法不适用。因此,我们只能排除整个依赖,或者尝试通过packagingOptions选择第一个。考虑到时间,建议先尝试移除ezviz-sdk-4.9.1.1,然后使用packagingOptionspickFirst来处理剩下的重复类。具体步骤:1.确保只使用ezviz-sdk的一个版本(例如5.20),移除对4.9.1.1的依赖。2.在app模块的build.gradle文件中,添加packagingOptions,使用pickFirst选择重复的类。注意:pickFirst的参数是类文件的路径(将包名的点替换为斜杠,内部类用$符号)。例如:```android{packagingOptions{pickFirst'org/MediaPlayer/PlayM4/PlayerCallBack$PlayerEncryptTypeCB.class'pickFirst'org/MediaPlayer/PlayM4/PlayerCallBack$PlayerFECDisplayCB.class'//...其他重复的类}}```但是,错误日志中列出了很多重复类,手动添加会很繁琐。而且,如果后续更新库增加了新的重复类,又会再次出错。3.如果重复类太多,也可以使用通配符,例如:```packagingOptions{pickFirst'org/MediaPlayer/PlayM4/**'}```但是,Android的packagingOptions不支持通配符在目录中间(只支持在末尾)。所以,我们可以这样写:```packagingOptions{pickFirst'org/MediaPlayer/PlayM4/PlayerCallBack$PlayerEncryptTypeCB.class'//每个重复的类都需要写一行}```注意:这种方法可能会隐藏问题,因为不同库中的相同类可能有不同的实现。因此,在解决后需要进行充分的测试。4.如果上述方法不可行,或者不想一个一个写,可以考虑移除ERTC_Android_SDK,或者使用其他方式。考虑到错误日志的最后一行提到了`PlayerSDK_hcnetsdk.jar`,其中也有重复的类。所以,我们可能也需要为这个jar包配置pickFirst。但是,如果这些库是互斥的,那么最好的办法是只保留一个库。结论:由于重复类问题通常是由于库之间的冲突造成的,建议优先考虑移除不必要的库,或者联系库的提供者解决冲突。如果必须保留所有库,那么使用packagingOptions的pickFirst是一种解决方案。下面是一个示例,展示如何为其中一个重复类设置pickFirst(其他重复类同理):在app的build.gradle文件中:```groovyandroid{//...其他配置packagingOptions{pickFirst'org/MediaPlayer/PlayM4/PlayerCallBack$PlayerEncryptTypeCB.class'pickFirst'org/MediaPlayer/PlayM4/PlayerCallBack$PlayerFECDisplayCB.class'pickFirst'org/MediaPlayer/PlayM4/PlayerCallBack$PlayerFileRefCB.class'pickFirst'org/MediaPlayer/PlayM4/PlayerCallBack$PlayerHSDetectCB.class'pickFirst'org/MediaPlayer/PlayM4/PlayerCallBack$PlayerIVSDrawFunCB.class'pickFirst'org/MediaPlayer/PlayM4/PlayerCallBack$PlayerLogCB.class'pickFirst'org/MediaPlayer/PlayM4/PlayerCallBack$PlayerPlayEndCB.class'pickFirst'org/MediaPlayer/PlayM4/PlayerCallBack$PlayerPreRecordCB.class'pickFirst'org/MediaPlayer/PlayM4/PlayerCallBack$PlayerPreRecordCBEx.class'pickFirst'org/MediaPlayer/PlayM4/PlayerCallBack$PlayerRunTimeInfoCB.class'pickFirst'org/MediaPlayer/PlayM4/PlayerCallBack$PlayerVideoFrameCB.class'pickFirst'org/MediaPlayer/PlayM4/PlayerCallBack$RealTimeRenderFrameRateCB.class'pickFirst'org/MediaPlayer/PlayM4/SurfaceCallBack.class'pickFirst'org/MediaPlayer/PlayM4/TimeStruct.class'}}```但是,请注意,错误日志中有些类在多个库中重复,我们只需要为每个重复的类指定一次pickFirst。另外,如果这些类在同一个包中还有其他类也是重复的,而我们没有列出,那么问题可能还会出现。因此,最彻底的方法是排除其中一个库。由于错误信息中提到了三个库,并且重复的类都在`org.MediaPlayer.PlayM4`包下,我们可以尝试排除其他库中这个包。但是,在Gradle中无法按包排除依赖,所以排除法不可行。因此,我们只能采取以下两种方法之一:1.使用packagingOptions,如上所示,为每个重复的类设置pickFirst。2.移除冲突的库,只保留一个。如果决定移除库,那么:1.移除旧版本的ezviz-sdk(4.9.1.1),因为已经有了5.20。2.如果ERTC_Android_SDK和ezviz-sdk-5.20功能重叠,考虑移除其中一个。如果无法移除,那么请使用packagingOptions方法。最后,关于PlayerSDK_hcnetsdk.jar,它只重复了一个类`TimeStruct`,我们已经在packagingOptions中处理了。注意:在修改后,需要重新构建项目。总结步骤:1.在build.gradle中移除对`com.ezviz.sdk:ezviz-sdk:4.9.1.1`的依赖。2.在app模块的build.gradle文件中,android块内添加packagingOptions,包含所有重复的类(如上所示)。3.重新构建项目。如果还有新的重复类出现,请根据错误日志继续添加。但是,请注意,packagingOptions只是告诉构建系统在遇到重复类时选择第一个,这可能会导致使用到某个库中的类并不是我们期望的版本,因此需要充分测试。如果问题依然存在,可能需要考虑使用其他库或联系SDK提供商。希望这些解决方案能帮助你解决问题。</think>### 解决 Android Studio 中重复类错误的分析与解决方案 这个错误表明您的项目中存在多个库包含了完全相同的类路径(`org.MediaPlayer.PlayM4` 下的类),导致 Gradle 无法确定该使用哪个库中的类。常见于同时引入了多个冲突的 SDK(如 ERTC SDK 和多个版本的 Ezviz SDK)。 #### 解决方案代码(在 `app/build.gradle` 中配置): ```groovy android { // 解决方案1:强制排除重复类所在的模块 configurations { all*.exclude group: 'com.ezviz.sdk', module: 'ezviz-sdk' // 排除旧版 SDK all*.exclude group: 'io.github.eviz-open', module: 'ezviz-sdk' // 排除新版 SDK } // 解决方案2:使用 pickFirst 选择首个匹配的类 packagingOptions { pickFirst 'org/MediaPlayer/PlayM4/PlayerCallBack$PlayerEncryptTypeCB.class' pickFirst 'org/MediaPlayer/PlayM4/TimeStruct.class' // 添加所有重复类路径(根据报错列表) } } dependencies { // 解决方案3:显式排除特定依赖中的冲突包 implementation ('com.required.library:core@aar') { exclude group: 'org.MediaPlayer', module: 'PlayM4' } // 只保留必要的 SDK 版本(示例) implementation 'io.github.ezviz-open:ezviz-sdk:5.20' // 保留最新版 implementation files('libs/ERTC_Android_SDK_1.5.0.1.aar') // 保留必要SDK } ``` ### 解决方案详解: 1. **排除冲突依赖(推荐)** 在 `configurations` 块中使用 `exclude` 移除不需要的 SDK 版本(如旧版 Ezviz SDK)。这是最彻底的解决方案。 2. **pickFirst 策略** 在 `packagingOptions` 中指定重复类的处理规则: - `pickFirst`:选择第一个找到的类文件(有兼容性风险) - 需要手动添加所有报错的类路径(将 `.` 替换为 `/`,`$` 保留) 3. **显式排除特定包** 在依赖声明中使用 `exclude` 移除具体冲突包(适合部分依赖的场景) 4. **统一 SDK 版本** 移除所有重复依赖,只保留一个版本的 SDK(如保留最新的 Ezviz SDK 5.20) ### 操作步骤: 1. 检查 `build.gradle` 文件,确认所有依赖项 2. 移除不必要的 SDK 依赖(特别是旧版 Ezviz SDK) 3. 选择上述一种解决方案配置 Gradle 4. 执行 **Clean Project** → **Rebuild Project** 5. 测试核心功能是否正常 ### 预防措施: 1. 使用 `./gradlew :app:dependencies` 查看依赖树 2. 添加新 SDK 时检查其包含的包路径 3. 优先使用官方 Maven 仓库而非本地 AAR/JAR
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值