Exemple #1
0
class NN:
    def __init__(self, config):
        self.config = config
        self.nLayer = config.getint("Architecture", "layer")
        self.nNodes = config.getint("Architecture", "nodes")
        self.nIter = config.getint("Architecture", "iterations")
        self.etha = config.getfloat("Factors", "initlearnrate")
        self.alpha = config.getfloat("Factors", "momentum")
        self.steepness = config.getfloat("Factors", "steepness")
        self.stepsizedec = config.getfloat("Factors", "stepsizedec")
        self.stepsizeinc = config.getfloat("Factors", "stepsizeinc")
        self.offset = config.getfloat("Factors", "initoffset")
        self.mindpp = config.getfloat("Thresholds", "mindpp")
        self.mindsse = config.getfloat("Thresholds", "mindsse")
        self.mindsumweights = config.getfloat("Thresholds", "mindsumweights")
        self.actfunc = config.get("Architecture", "activation")
        self.weightsinit = config.get("Architecture", "initweights")
        self.errfct = config.get("Architecture", "errorfunction")
        self.metrics = Metric(config.get("Output", "metrics"), config.getint("Output", "metricsclass"))
        self.verbosity = config.getint("Output", "verbosity")
        self.interactive = config.getboolean("Output", "interactive")

        self.weights = []
        self.outs = []
        self.deltas = []

        self.generateActivationFunction()

    ##############################################################################

    def generateActivationFunction(self):

        if self.actfunc == "logistic":

            def dphi(net):
                r = 1.0 / (1.0 + numpy.exp(-net * self.steepness))
                return numpy.multiply(r, (1.0 - r))

            self.phi = lambda net: 1.0 / (1.0 + numpy.exp(-net * self.steepness))
            self.dphi = dphi

        elif self.actfunc == "tanh":
            self.phi = lambda net: numpy.tanh(self.steepness * net)
            self.dphi = lambda net: self.steepness * (1.0 - numpy.power(numpy.tanh(net), 2))

        elif self.actfunc == "linear":
            self.phi = lambda net: self.steepness * net
            self.dphi = lambda net: self.steepness

        elif self.actfunc == "softmax":

            def phi(net):
                s = 1.0 / numpy.exp(-net).sum()
                return s * numpy.exp(-net)

            self.phi = foo

            def dphi(net):
                r = self.phi(net)
                return numpy.multiply(r, (1.0 - r))

            self.dphi = dphi

        elif self.actfunc == "gauss":
            self.phi = lambda net: numpy.exp(-numpy.power(net - 1, 2) * self.steepness)
            self.dphi = lambda net: -2 * numpy.multiply(net - 1, numpy.exp(-numpy.power(net - 1, 2)))

        elif self.actfunc == "sin":
            self.phi = lambda net: numpy.sin(self.steepness * net)
            self.dphi = lambda net: self.steepness * numpy.cos(self.steepness * net)
        else:
            logging.error("Unknown activation function. Available: logistic, tanh, linear, softmax, gauss, sin")
            sys.exit(-1)

    ##############################################################################

    def reload(self, config, weights):
        self.__init__(config)
        self.weights = weights

    ##############################################################################

    def initWeights(self, cls, feat):
        self.nIn = feat
        self.nOut = cls

        def initWeights(generateMatrixFunc):
            self.weights.append(generateMatrixFunc(self.nIn, self.nNodes))
            for i in range(1, self.nLayer):
                self.weights.append(generateMatrixFunc(self.nNodes, self.nNodes))
            self.weights.append(generateMatrixFunc(self.nNodes, self.nOut))

        if self.weightsinit == "randuni":

            def mat(n, m):
                return self.offset * (numpy.mat(numpy.random.rand(n, m)) + 0.5)

        elif self.weightsinit == "randgauss":

            def mat(n, m):
                return self.offset * numpy.mat(numpy.random.standard_normal([n, m]))

        elif self.weightsinit == "uniform":

            def mat(n, m):
                return self.offset * numpy.mat(numpy.ones([n, m]))

        elif self.weightsinit == "exponential":

            def mat(n, m):
                return self.offset * numpy.mat(numpy.random.standard_exponential(size=[n, m]))

        else:
            logging.error("Unknown weights initialization. Available: randuni, randgauss, uniform, exponential")
            sys.exit(-1)

        initWeights(mat)

        from copy import copy

        self.lastchange = copy(self.weights)

        self.outs = [None] * (self.nLayer + 1)
        self.deltas = [None] * (self.nLayer + 1)

    ##############################################################################

    def test(self, data):
        conf = numpy.zeros([self.nOut, self.nOut], numpy.int16)
        allprobs = [None] * len(data)
        for i, row in enumerate(data):
            allprobs[i] = self.passForward(row)
            conf[data.targets[i], allprobs[i].argmax()] += 1
            # TODO: not needed?
            allprobs[i] /= allprobs[i].sum()

        return conf, 1 - conf.trace() / float(conf.sum()), allprobs

    ##############################################################################

    def passForward(self, row):
        # input
        sum = row * self.weights[0]
        self.outs[0] = (sum, self.phi(sum))

        # next layers
        for w in range(1, self.nLayer + 1):
            sum = self.outs[w - 1][1] * self.weights[w]
            self.outs[w] = (sum, self.phi(sum))

        return self.outs[-1][1][0]

    ##############################################################################

    def train(self, data):
        sse = sys.maxint
        pp = sys.maxint

        self.initWeights(data.cls, data.feat)

        interactive = self.interactive and os.isatty(sys.stdout.fileno())

        ref = numpy.zeros([1, self.nOut])
        c_old = 0
        allprobs = [None] * len(data)

        for i in range(self.nIter):
            conf = numpy.zeros([self.nOut, self.nOut])

            sumold = sse
            ppold = pp
            sse = 0.0
            sce = 0.0
            if interactive:
                pbar = ProgressBar(maxval=len(data)).start()
            for k, row in enumerate(data):
                probs = self.passForward(row)
                ref[0, c_old] = 0
                ref[0, data.targets[k]] = 1
                c_old = data.targets[k]

                diff = ref - probs
                if self.errfct == "sse":
                    self.deltas[-1] = numpy.multiply(diff, self.dphi(probs))
                    sse += numpy.power(diff, 2).sum()
                elif self.errfct == "sce":
                    self.deltas[-1] = diff * self.steepness

                    # cross entropy: 1/C * sum{ (tk*log(yk)) + (1-tk)*log(1-yk) }
                    sce -= (
                        (numpy.multiply(ref, numpy.log(probs)) + numpy.multiply((1 - ref), numpy.log(1 - probs)))
                    ).sum() / self.nOut

                weightschange = self.passBackward(row)
                if interactive:
                    pbar.update(k)

                # train statistics
                c_pred = probs.argmax()
                conf[data.targets[k], c_pred] += 1
                allprobs[k] = probs

            # conf_, err, tepr = self.test( testdata )
            # conf_, err, tepr = self.test( data )
            output = self.metrics.obtain(data, allprobs, conf, 1 - conf.trace() / float(conf.sum()))

            if self.errfct == "sse":
                output["errfct"] = "SSE: % 6.4f" % sse
            elif self.errfct == "sce":
                output["errfct"] = "SCE: % 6.4f" % sce

            if interactive:
                pbar.finish()
            metrics = "%(lift)s%(pp)s%(fscore)s%(tester)s%(auc)s" % output
            logging.warning(
                "iter: % 4d er: %.6f %s rate: %.4f%s",
                i + 1,
                1 - conf.trace() / conf.sum(),
                output["errfct"],
                self.etha,
                metrics,
            )

            # for i in range(len(self.weights)):
            #    print "pruned:", (numpy.abs(self.weights[i])<0.1).sum()
            #    self.weights[i][numpy.abs(self.weights[i])<0.1]=0
            # if weightschange < self.mindsumweights:
            #    self.weights[-1] = self.weights[-1] + numpy.random.standard_normal([self.nNodes, self.nOut]) * 0.1
            #    logging.warning("disturbing weights for leaving local optimum...")

            if sumold - sse < self.mindsse or ppold - pp < self.mindpp:
                self.etha *= self.stepsizedec
            else:
                self.etha *= self.stepsizeinc
        return allprobs

    ##############################################################################

    def passBackward(self, row):
        # precompute deltas for the inner layers
        for l in range(self.nLayer)[::-1]:
            self.deltas[l] = self.deltas[l + 1] * self.weights[l + 1].T
            self.deltas[l] = numpy.multiply(self.deltas[l], self.dphi(self.outs[l][0]))
            # for i in range(self.nNodes):
            #    self.deltas[l][i] = 0.0
            #    for j in range(len(self.deltas[l+1])):
            #        self.deltas[l][i] += self.deltas[l+1][j] * self.weights[l+1][i,j]
            #    self.deltas[l][i] *= self.dphi( self.outs[l][i][0] )
        # self.etha *= (1-self.alpha)
        # output layer
        delta = self.etha * numpy.outer(self.outs[-2][1], self.deltas[-1]) + self.alpha * self.lastchange[-1]
        self.weights[-1] = self.weights[-1] + delta
        self.lastchange[-1] = delta
        # for j in range(self.nOut):
        #    f = self.etha * self.deltas[-1][j]
        #    for i in range( self.nNodes ):
        #        self.weights[-1][i,j] += f * self.outs[-2][i][1]

        # recalculate weights forwards
        # inner layers
        for l in range(1, self.nLayer):
            # for j in range(self.nNodes):
            #    f = self.etha * self.deltas[l][j]
            #    for i in range (self.nNodes):
            #        self.weights[l][i,j] += f * self.outs[l-1][i][1]
            delta = (1 - self.alpha) * self.etha * numpy.outer(
                self.outs[l - 1][1], self.deltas[l]
            ) + self.alpha * self.lastchange[l]
            self.weights[l] = self.weights[l] + delta
            self.lastchange[l] = delta

        # input vector once again influences w'
        # for j in range(self.nNodes):
        #    f = self.etha * self.deltas[0][j]
        #    for i in range(self.nIn):
        #        self.weights[0][i,j] += f * row[i]
        delta = (1 - self.alpha) * self.etha * numpy.outer(row, self.deltas[0]) + self.alpha * self.lastchange[0]
        self.weights[0] = self.weights[0] + delta
        self.lastchange[0] = delta

        return sum([d.sum() for d in self.lastchange])

    ##############################################################################

    def savemodel(self, modelname):
        import pickle

        model = (self.weights, self.config)
        pickle.dump(model, open(modelname, "w"))

    def loadmodel(self, modelname):
        import pickle

        self.weights, self.config = pickle.load(file(modelname))
        self.reload(self.config, self.weights)