Skip to content
This repository was archived by the owner on Jul 1, 2023. It is now read-only.

Commit b5088a7

Browse files
eaplataniosrxwei
authored andcommitted
Add 'logSumExp'. (#150)
1 parent 0862d9b commit b5088a7

File tree

2 files changed

+140
-3
lines changed

2 files changed

+140
-3
lines changed

Sources/TensorFlow/Operators/Math.swift

Lines changed: 119 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1874,10 +1874,9 @@ public extension Tensor where Scalar: TensorFlowFloatingPoint {
18741874
standardDeviation(squeezingAxes: axes)
18751875
}
18761876

1877-
/// Returns the standard deviation of the elements along the specified axes. The reduced
1878-
/// dimensions are retained with value `1`. Does not apply Bessel's correction.
1877+
/// Returns the standard deviation of all elements in this tensor.
1878+
/// Does not apply Bessel's correction.
18791879
///
1880-
/// - Parameter axes: The dimensions to reduce.
18811880
/// - Precondition: Each value in `axes` must be in the range `-rank..<rank`.
18821881
@inlinable
18831882
@differentiable(wrt: self)
@@ -1920,6 +1919,123 @@ public extension Tensor where Scalar: TensorFlowFloatingPoint {
19201919
func standardDeviation(alongAxes axes: Int...) -> Tensor {
19211920
TensorFlow.sqrt(variance(alongAxes: axes))
19221921
}
1922+
1923+
/// Returns `log(exp(self).sum(squeezingAxes: axes))`. The reduced dimensions are removed.
1924+
///
1925+
/// This function is more numerically stable than computing
1926+
/// `log(exp(self).sum(squeezingAxes: axes))` directly. It avoids overflows caused by computing
1927+
/// the `exp` of large inputs and underflows caused by computing the `log` of small inputs.
1928+
///
1929+
/// - Parameter axes: The dimensions to reduce.
1930+
/// - Precondition: Each value in `axes` must be in the range `-rank..<rank`.
1931+
@inlinable
1932+
@differentiable(wrt: self)
1933+
func logSumExp(squeezingAxes axes: Tensor<Int32>) -> Tensor {
1934+
let rawMax = max(alongAxes: axes)
1935+
let offset = Swift.withoutDerivative(at: rawMax) { rawMax in
1936+
rawMax.replacing(
1937+
with: Tensor<Scalar>(zerosLike: rawMax),
1938+
where: rawMax.isFinite)
1939+
}
1940+
let result = TensorFlow.log(TensorFlow.exp(self - offset).sum(squeezingAxes: axes))
1941+
let resultShape = Swift.withoutDerivative(at: result.shapeTensor, in: identity)
1942+
return result + offset.reshaped(toShape: resultShape)
1943+
}
1944+
1945+
/// Returns `log(exp(self).sum(squeezingAxes: axes))`. The reduced dimensions are removed.
1946+
///
1947+
/// This function is more numerically stable than computing
1948+
/// `log(exp(self).sum(squeezingAxes: axes))` directly. It avoids overflows caused by computing
1949+
/// the `exp` of large inputs and underflows caused by computing the `log` of small inputs.
1950+
///
1951+
/// - Parameter axes: The dimensions to reduce.
1952+
/// - Precondition: Each value in `axes` must be in the range `-rank..<rank`.
1953+
@inlinable
1954+
@differentiable(wrt: self)
1955+
func logSumExp(squeezingAxes axes: [Int]) -> Tensor {
1956+
// TODO(TF-433): Remove workaround for differentiating `map`.
1957+
let axes = Swift.withoutDerivative(at: axes) { $0.map(Int32.init) }
1958+
return logSumExp(squeezingAxes: Tensor<Int32>(axes))
1959+
}
1960+
1961+
/// Returns `log(exp(self).sum(squeezingAxes: axes))`. The reduced dimensions are removed.
1962+
///
1963+
/// This function is more numerically stable than computing
1964+
/// `log(exp(self).sum(squeezingAxes: axes))` directly. It avoids overflows caused by computing
1965+
/// the `exp` of large inputs and underflows caused by computing the `log` of small inputs.
1966+
///
1967+
/// - Parameter axes: The dimensions to reduce.
1968+
/// - Precondition: Each value in `axes` must be in the range `-rank..<rank`.
1969+
@inlinable
1970+
@differentiable(wrt: self)
1971+
func logSumExp(squeezingAxes axes: Int...) -> Tensor {
1972+
return logSumExp(squeezingAxes: axes)
1973+
}
1974+
1975+
/// Returns `log(exp(self).sum())`. The result is a scalar.
1976+
///
1977+
/// This function is more numerically stable than computing `log(exp(self).sum())` directly. It
1978+
/// avoids overflows caused by computing the `exp` of large inputs and underflows caused by
1979+
/// computing the `log` of small inputs.
1980+
@inlinable
1981+
@differentiable(wrt: self)
1982+
func logSumExp() -> Tensor {
1983+
return logSumExp(squeezingAxes: Array(0..<shape.rank))
1984+
}
1985+
1986+
/// Returns `log(exp(self).sum(alongAxes: axes))`. The reduced dimensions are retained with
1987+
/// value `1`.
1988+
///
1989+
/// This function is more numerically stable than computing
1990+
/// `log(exp(self).sum(alongAxes: axes))` directly. It avoids overflows caused by computing
1991+
/// the `exp` of large inputs and underflows caused by computing the `log` of small inputs.
1992+
///
1993+
/// - Parameter axes: The dimensions to reduce.
1994+
/// - Precondition: Each value in `axes` must be in the range `-rank..<rank`.
1995+
@inlinable
1996+
@differentiable(wrt: self)
1997+
func logSumExp(alongAxes axes: Tensor<Int32>) -> Tensor {
1998+
let rawMax = max(alongAxes: axes)
1999+
let offset = Swift.withoutDerivative(at: rawMax) { rawMax in
2000+
rawMax.replacing(
2001+
with: Tensor<Scalar>(zerosLike: rawMax),
2002+
where: rawMax.isFinite)
2003+
}
2004+
let result = TensorFlow.log(TensorFlow.exp(self - offset).sum(alongAxes: axes))
2005+
return result + offset
2006+
}
2007+
2008+
/// Returns `log(exp(self).sum(alongAxes: axes))`. The reduced dimensions are retained with
2009+
/// value `1`.
2010+
///
2011+
/// This function is more numerically stable than computing
2012+
/// `log(exp(self).sum(alongAxes: axes))` directly. It avoids overflows caused by computing
2013+
/// the `exp` of large inputs and underflows caused by computing the `log` of small inputs.
2014+
///
2015+
/// - Parameter axes: The dimensions to reduce.
2016+
/// - Precondition: Each value in `axes` must be in the range `-rank..<rank`.
2017+
@inlinable
2018+
@differentiable(wrt: self)
2019+
func logSumExp(alongAxes axes: [Int]) -> Tensor {
2020+
// TODO(TF-433): Remove workaround for differentiating `map`.
2021+
let axes = Swift.withoutDerivative(at: axes) { $0.map(Int32.init) }
2022+
return logSumExp(alongAxes: Tensor<Int32>(axes))
2023+
}
2024+
2025+
/// Returns `log(exp(self).sum(alongAxes: axes))`. The reduced dimensions are retained with
2026+
/// value `1`.
2027+
///
2028+
/// This function is more numerically stable than computing
2029+
/// `log(exp(self).sum(alongAxes: axes))` directly. It avoids overflows caused by computing
2030+
/// the `exp` of large inputs and underflows caused by computing the `log` of small inputs.
2031+
///
2032+
/// - Parameter axes: The dimensions to reduce.
2033+
/// - Precondition: Each value in `axes` must be in the range `-rank..<rank`.
2034+
@inlinable
2035+
@differentiable(wrt: self)
2036+
func logSumExp(alongAxes axes: Int...) -> Tensor {
2037+
return logSumExp(alongAxes: axes)
2038+
}
19232039
}
19242040

19252041
//===------------------------------------------------------------------------------------------===//

Tests/TensorFlowTests/OperatorTests/MathTests.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,26 @@ final class MathOperatorTests: XCTestCase {
205205
XCTAssertEqual(scalarsArgmax.array, ShapedArray(shape: [], scalars: [5]))
206206
}
207207

208+
func testLogSumExp() {
209+
let x = Tensor<Float>([
210+
[0.45031791, 0.41123222, 0.53928467, 0.47167023, 0.15483777],
211+
[0.49975705, 0.71807549, 0.30396056, 0.2690469 , 0.01404393],
212+
[0.16950939, 0.41085612, 0.79503016, 0.11977817, 0.99728241],
213+
[0.62510073, 0.17344792, 0.1540605 , 0.40758517, 0.93683817],
214+
[0.15653343, 0.50502756, 0.99365925, 0.84617581, 0.17422509]])
215+
let y0 = x.logSumExp()
216+
let y1 = x.logSumExp(squeezingAxes: 1)
217+
let y2 = x.logSumExp(alongAxes: 1)
218+
let expectedY0 = Tensor<Float>(3.713885997817954)
219+
let expectedY1 = Tensor<Float>(
220+
[2.02318908, 1.99835067, 2.16853826, 2.1137799, 2.20261244])
221+
let expectedY2 = Tensor<Float>(
222+
[[2.02318908], [1.99835067], [2.16853826], [2.1137799], [2.20261244]])
223+
assertEqual(y0, expectedY0, accuracy: 0.0001)
224+
assertEqual(y1, expectedY1, accuracy: 0.0001)
225+
assertEqual(y2, expectedY2, accuracy: 0.0001)
226+
}
227+
208228
func testCeilAndFloor() {
209229
let x = Tensor<Float>([-1.3, -0.4, 0.5, 1.6])
210230
let xFloor = floor(x)
@@ -357,6 +377,7 @@ final class MathOperatorTests: XCTestCase {
357377
("testCosineSimilarity", testCosineSimilarity),
358378
("testReduction", testReduction),
359379
("testArgmax", testArgmax),
380+
("testLogSumExp", testLogSumExp),
360381
("testCeilAndFloor", testCeilAndFloor),
361382
("testSimpleMath", testSimpleMath),
362383
("testStandardDeviation", testStandardDeviation),

0 commit comments

Comments
 (0)