* Computes the output shape of the layer. * * Assumes that the layer will be built to match that input shape provided. * * @param inputShape A shape (tuple of integers) or a list of shape tuples * (one per output tensor of the layer). Shape tuples can include null for * free di
(inputShape: Shape|Shape[])
| 781 | * free dimensions, instead of an integer. |
| 782 | */ |
| 783 | override computeOutputShape(inputShape: Shape|Shape[]): Shape|Shape[] { |
| 784 | const inputShapes = types_utils.normalizeShapeList(inputShape); |
| 785 | if (inputShapes.length !== this.inputLayers.length) { |
| 786 | throw new ValueError( |
| 787 | `Invalid inputShape argument ${inputShape}: ` + |
| 788 | `model has ${this.inputLayers.length} tensor inputs.`); |
| 789 | } |
| 790 | |
| 791 | // TODO(michaelterry): Add caching |
| 792 | const layersToOutputShapes: {[shapeKey: string]: Shape} = {}; |
| 793 | for (let i = 0; i < inputShapes.length; i++) { |
| 794 | const layer = this.inputLayers[i]; |
| 795 | const inputShape = inputShapes[i]; |
| 796 | // It's an input layer: computeOutputShape is identity, |
| 797 | // and there is only one node and one tensor output. |
| 798 | const shapeKey = layer.name + '_0_0'; |
| 799 | layersToOutputShapes[shapeKey] = inputShape; |
| 800 | } |
| 801 | |
| 802 | const depthKeys = Object.keys(this.nodesByDepth) |
| 803 | .map(x => parseInt(x, 10)) |
| 804 | .sort(generic_utils.reverseNumberCompare); |
| 805 | // Iterate over nodes, by depth level. |
| 806 | if (depthKeys.length > 1) { |
| 807 | for (const depth of depthKeys) { |
| 808 | const nodes = this.nodesByDepth[depth]; |
| 809 | for (const node of nodes) { |
| 810 | // This is always a single layer, never a list. |
| 811 | const layer = node.outboundLayer; |
| 812 | if (this.inputLayers.map(x => x.id).indexOf(layer.id) !== -1) { |
| 813 | // We've already covered the input layers a few lines above. |
| 814 | continue; |
| 815 | } |
| 816 | // Potentially redundant list, same size of node.inputTensors. |
| 817 | const inputShapes: Shape[] = []; |
| 818 | for (let j = 0; j < node.inboundLayers.length; j++) { |
| 819 | const inboundLayer = node.inboundLayers[j]; |
| 820 | const nodeIndex = node.nodeIndices[j]; |
| 821 | const tensorIndex = node.tensorIndices[j]; |
| 822 | const shapeKey = `${inboundLayer.name}_${nodeIndex}_${tensorIndex}`; |
| 823 | const inputShape = layersToOutputShapes[shapeKey]; |
| 824 | inputShapes.push(inputShape); |
| 825 | } |
| 826 | |
| 827 | const outputShape = layer.computeOutputShape( |
| 828 | generic_utils.singletonOrArray(inputShapes)); |
| 829 | |
| 830 | const outputShapes = types_utils.normalizeShapeList(outputShape); |
| 831 | const nodeIndex = layer.inboundNodes.indexOf(node); |
| 832 | for (let j = 0; j < outputShapes.length; j++) { |
| 833 | const shapeKey = `${layer.name}_${nodeIndex}_${j}`; |
| 834 | layersToOutputShapes[shapeKey] = outputShapes[j]; |
| 835 | } |
| 836 | } |
| 837 | } |
| 838 | } |
| 839 | |
| 840 | // Read final output shapes from layersToOutputShapes. |
nothing calls this directly
no test coverage detected
searching dependent graphs…