Tensorflow NaN bug?

时间:2015-11-14 19:01:58

标签: nan tensorflow

我正在使用TensorFlow,我修改了tutorial示例以获取我的RGB图像。

该算法在新图像集上完美无缺地运行,直到突然(仍然会聚,通常精度约为92%),它与ReluGrad接收到非有限值的错误一起崩溃。调试显示数字没有异常发生,直到非常突然,由于未知原因,错误被抛出。添加

print "max W vales: %g %g %g %g"%(tf.reduce_max(tf.abs(W_conv1)).eval(),tf.reduce_max(tf.abs(W_conv2)).eval(),tf.reduce_max(tf.abs(W_fc1)).eval(),tf.reduce_max(tf.abs(W_fc2)).eval())
print "max b vales: %g %g %g %g"%(tf.reduce_max(tf.abs(b_conv1)).eval(),tf.reduce_max(tf.abs(b_conv2)).eval(),tf.reduce_max(tf.abs(b_fc1)).eval(),tf.reduce_max(tf.abs(b_fc2)).eval())

作为每个循环的调试代码,产生以下输出:

Step 8600
max W vales: 0.759422 0.295087 0.344725 0.583884
max b vales: 0.110509 0.111748 0.115327 0.124324
Step 8601
max W vales: 0.75947 0.295084 0.344723 0.583893
max b vales: 0.110516 0.111753 0.115322 0.124332
Step 8602
max W vales: 0.759521 0.295101 0.34472 0.5839
max b vales: 0.110521 0.111747 0.115312 0.124365
Step 8603
max W vales: -3.40282e+38 -3.40282e+38 -3.40282e+38 -3.40282e+38
max b vales: -3.40282e+38 -3.40282e+38 -3.40282e+38 -3.40282e+38

由于我的值都不是很高,NaN可能发生的唯一方法是处理不当的0/0,但由于本教程代码没有进行任何划分或类似的操作,我看不到其他解释这来自内部TF代码。

我对如何处理这件事毫无头绪。有什么建议?该算法很好地融合,它在我的验证集上的准确性稳步攀升,在迭代8600时达到了92.5%。

15 个答案:

答案 0 :(得分:127)

实际上,结果证明是愚蠢的。我发布这个以防万一其他人会遇到类似的错误。

cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv))

实际上是计算交叉熵的一种可怕方式。在一些样本中,某些类可以在一段时间后确定地排除,导致该样本的y_conv = 0。这通常不是问题,因为你对那些不感兴趣,但是在那里写入cross_entropy的方式,它为该特定样本/类产生0 * log(0)。因此,NaN。

替换它
cross_entropy = -tf.reduce_sum(y_*tf.log(tf.clip_by_value(y_conv,1e-10,1.0)))

解决了我所有的问题。

答案 1 :(得分:26)

实际上,裁剪不是一个好主意,因为它会阻止渐变在达到阈值时向后传播。相反,我们可以在softmax输出中添加一点常量。

cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv + 1e-10))

答案 2 :(得分:17)

无偏见的替代方案。

许多其他解决方案使用裁剪来避免未定义的渐变。根据您的问题,裁剪会引入偏差,并且在所有情况下都可能无法接受。如下面的代码所示,我们只需要处理不连续点 - 而不是它附近的区域。

具体答案

def cross_entropy(x, y, axis=-1):
  safe_y = tf.where(tf.equal(x, 0.), tf.ones_like(y), y)
  return -tf.reduce_sum(x * tf.log(safe_y), axis)

def entropy(x, axis=-1):
  return cross_entropy(x, x, axis)

但它有效吗?

x = tf.constant([0.1, 0.2, 0., 0.7])
e = entropy(x)
# ==> 0.80181855
g = tf.gradients(e, x)[0]
# ==> array([1.30258512,  0.60943794, 0., -0.64332503], dtype=float32)  Yay! No NaN.

(注意:已删除dup cross-post。)

一般食谱

使用内部tf.where确保函数没有渐近线。 也就是说,改变inf生成函数的输入,使得不能创建inf。 然后使用第二个tf.where始终选择有效的代码路径。 也就是说,按照“正常”的方式实现数学条件,即“天真”实现。

在Python代码中,配方是:

而不是:

tf.where(x_ok, f(x), safe_f(x))

这样做:

safe_x = tf.where(x_ok, x, safe_x)
tf.where(x_ok, f(safe_x), safe_f(x))

实施例

假设你想计算:

f(x) = { 1/x, x!=0
       { 0,   x=0

天真的实现会在梯度中产生NaN,即

def f(x):
  x_ok = tf.not_equal(x, 0.)
  f = lambda x: 1. / x
  safe_f = tf.zeros_like
  return tf.where(x_ok, f(x), safe_f(x))

有效吗?

x = tf.constant([-1., 0, 1])
tf.gradients(f(x), x)[0].eval()
# ==> array([ -1.,  nan,  -1.], dtype=float32)
#  ...bah! We have a NaN at the asymptote despite not having
# an asymptote in the non-differentiated result.

使用tf.where时避免NaN渐变的基本模式是两次调用tf.where。最里面的tf.where确保结果f(x)始终是有限的。最外面的tf.where确保选择正确的结果。对于运行示例,技巧如下:

def safe_f(x):
  x_ok = tf.not_equal(x, 0.)
  f = lambda x: 1. / x
  safe_f = tf.zeros_like
  safe_x = tf.where(x_ok, x, tf.ones_like(x))
  return tf.where(x_ok, f(safe_x), safe_f(x))

但它有效吗?

x = tf.constant([-1., 0, 1])
tf.gradients(safe_f(x), x)[0].eval()
# ==> array([-1.,  0., -1.], dtype=float32)
# ...yay! double-where trick worked. Notice that the gradient
# is now a constant at the asymptote (as opposed to being NaN).

答案 3 :(得分:13)

如果y_conv是softmax的结果,比如y_conv = tf.nn.softmax(x),那么更好的解决方案是将其替换为log_softmax

y = tf.nn.log_softmax(x)
cross_entropy = -tf.reduce_sum(y_*y)

答案 4 :(得分:1)

您正尝试使用标准公式计算cross-entropy。 <{1}}时不仅值不确定,而且在数值上也不稳定。

最好使用tf.nn.softmax_cross_entropy_with_logits或者如果您真的想使用手工制作的公式,请将tf.clip_by_value零写入日志中的非常小的数字。

答案 5 :(得分:1)

Here is the implementation of the binary (sigmoid) and categorical (softmax) cross-entropy losses in TensorFlow 1.1:

As one can see in the binary case they consider some special cases to achieve numerical stability:

# The logistic loss formula from above is
#   x - x * z + log(1 + exp(-x))
# For x < 0, a more numerically stable formula is
#   -x * z + log(1 + exp(x))
# Note that these two expressions can be combined into the following:
#   max(x, 0) - x * z + log(1 + exp(-abs(x)))
# To allow computing gradients at zero, we define custom versions of max and
# abs functions.
zeros = array_ops.zeros_like(logits, dtype=logits.dtype)
cond = (logits >= zeros)
relu_logits = array_ops.where(cond, logits, zeros)
neg_abs_logits = array_ops.where(cond, -logits, logits)
return math_ops.add(relu_logits - logits * labels,
                    math_ops.log1p(math_ops.exp(neg_abs_logits)),
                    name=name)

答案 6 :(得分:1)

我使用LSTM进行长序列并获得了纳米梯度。这些答案都没有帮助我。但我想出了三个自己的解决方案。我希望它们对从谷歌搜索来到这里的其他人有用。

  1. 渐变剪辑对我没有帮助,因为渐变在一次批量更新中变为nan。在这种情况下,您可以使用以下行替换nans:

    was_nan = tf.reduce_any(tf.convert_to_tensor([tf.reduce_any(tf.is_nan(g)) for g in grads]))
    

    如果您想跟踪nans是否出现,您可以使用此代码:

    LayerNormBasicLSTMCell(neurons, dropout_keep_prob=0.8)
    
  2. 将LSTMCell替换为LayerNormBasicLSTMCell - 具有图层规范的LSTM单元格 - 类似于时间步之间的批量规范。

  3. 如果您使用常规的复发状态丢失,您可以将其替换为&#34; Recurrent Dropout without Memory Loss&#34;。代码:

    LayerNormBasicLSTMCell(neurons, layer_norm=False, dropout_keep_prob=0.8)
    

    请注意,您也可以单独打开Dropout功能而不进行图层规范化:

    public class HomeFragment2 extends Fragment {
    
    List<DataAdapter> ListOfdataAdapter;
    RecyclerView recyclerView;
    String HTTP_JSON_URL = "http://androidblog.esy.es/ImageJsonData.php";
    String Image_Name_JSON = "image_title";
    String Image_URL_JSON = "image_url";
    JsonArrayRequest RequestOfJSonArray ;
    RequestQueue requestQueue ;
    View view ;
    int RecyclerViewItemPosition ;
    RecyclerView.LayoutManager layoutManagerOfrecyclerView;
    RecyclerView.Adapter recyclerViewadapter;
    ArrayList<String> ImageTitleNameArrayListForClick;
    
    
    public static HomeFragment2 newInstance() {
    
    
        return new HomeFragment2();
    }
    
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
    
        View v = inflater.inflate(R.layout.fragment_home2, container, false);
    
        ImageTitleNameArrayListForClick = new ArrayList<>();
    
        ListOfdataAdapter = new ArrayList<>();
    
        recyclerView =getActivity(). findViewById(R.id.recyclerview1);
    
        recyclerView.setHasFixedSize(true);
    
        layoutManagerOfrecyclerView = new LinearLayoutManager(getActivity());
    
        recyclerView.setLayoutManager(layoutManagerOfrecyclerView);
    
        JSON_HTTP_CALL();
    
        // Implementing Click Listener on RecyclerView.
        recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
    
            GestureDetector gestureDetector = new GestureDetector(getActivity(), new GestureDetector.SimpleOnGestureListener() {
    
                @Override public boolean onSingleTapUp(MotionEvent motionEvent) {
    
                    return true;
                }
    
            });
            @Override
            public boolean onInterceptTouchEvent(RecyclerView Recyclerview, MotionEvent motionEvent) {
    
                view = Recyclerview.findChildViewUnder(motionEvent.getX(), motionEvent.getY());
    
                if(view != null && gestureDetector.onTouchEvent(motionEvent)) {
    
                    //Getting RecyclerView Clicked Item value.
                    RecyclerViewItemPosition = Recyclerview.getChildAdapterPosition(view);
    
                    // Showing RecyclerView Clicked Item value using Toast.
                    Toast.makeText(getActivity(), ImageTitleNameArrayListForClick.get(RecyclerViewItemPosition), Toast.LENGTH_LONG).show();
                }
    
                return false;
            }
    
            @Override
            public void onTouchEvent(RecyclerView Recyclerview, MotionEvent motionEvent) {
    
            }
    
            @Override
            public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    
            }
        });
    
        return v;
    
    }
    
    public void JSON_HTTP_CALL(){
    
        RequestOfJSonArray = new JsonArrayRequest(HTTP_JSON_URL,
    
                new Response.Listener<JSONArray>() {
                    @Override
                    public void onResponse(JSONArray response) {
    
                        ParseJSonResponse(response);
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
    
                    }
                });
    
        requestQueue = Volley.newRequestQueue(getActivity());
    
        requestQueue.add(RequestOfJSonArray);
    }
    
    public void ParseJSonResponse(JSONArray array){
    
        for(int i = 0; i<array.length(); i++) {
    
            DataAdapter GetDataAdapter2 = new DataAdapter();
    
            JSONObject json = null;
            try {
    
                json = array.getJSONObject(i);
    
                GetDataAdapter2.setImageTitle(json.getString(Image_Name_JSON));
    
                // Adding image title name in array to display on RecyclerView click event.
                ImageTitleNameArrayListForClick.add(json.getString(Image_Name_JSON));
    
                GetDataAdapter2.setImageUrl(json.getString(Image_URL_JSON));
    
            } catch (JSONException e) {
    
                e.printStackTrace();
            }
            ListOfdataAdapter.add(GetDataAdapter2);
        }
    
        recyclerViewadapter = new RecyclerViewAdapter(ListOfdataAdapter, getActivity());
    
        recyclerView.setAdapter(recyclerViewadapter);
    }
    }
    

答案 7 :(得分:1)

有时您在使用tf.sqrt()函数时未在其中添加小的常量1e-10,从而引发了nan问题。

答案 8 :(得分:1)

2.0兼容答案:用于从

迁移@ user1111929的答案的代码

Tensorflow 1.xTensorflow 2.x,如下所示:

Tensorflow 1.x

cross_entropy = -tf.reduce_sum(y_*tf.log(tf.clip_by_value(y_conv,1e-10,1.0)))

Tensorflow 2.x

cross_entropy = -tf.compat.v2.reduce_sum(y_*tf.log(tf.compat.v2.clip_by_value(y_conv,1e-10,1.0)))

cross_entropy = -tf.compat.v2.math.reduce_sum(y_*tf.log(tf.compat.v1.clip_by_value(y_conv,1e-10,1.0)))

答案 9 :(得分:0)

在制作标准的前馈网络时,我有时而不是其他时间。我之前使用过类似的TensorFlow代码,但它运行良好。

事实证明我偶然导入了变量名称。因此,只要批量选择第一行(变量名称),就会开始出现纳米损失。也许要留意那个?

答案 10 :(得分:0)

除了上述所有出色的答案之外,我还要添加我的内容。这种情况不太常见,但却会导致NaN:除以零

在我的NLP任务网络中,有一层进行平均池化。即,每个数据是令牌序列。我的图层做了一些令牌嵌入,然后计算嵌入向量的平均值。

平均计算编码为

tf.reduce_sum(embedded)/tf.reduce_sum(tf.not_equal(input, pad)) 

这里pad是我在批处理中使用的一些虚拟令牌。

现在,如果某些数据包含空令牌列表(无论出于何种原因),则其长度(上面的代码片段中的分母)将为0。这将导致除以零问题和NaN将保留在以下所有层/优化步骤中。

万一有人遇到这个问题,我用tf.where来解决这些问题:

sum_embedding = tf.reduce_sum(embedded, 1)
embedding_length = tf.reduce_sum(tf.cast(tf.not_equal(input, pad), dtype=tf.float32), axis=1, keep_dims=True)
embedding_length_smoothed = tf.where(tf.greater(embedding_length, 0.0), embedding_length, tf.ones(tf.shape(embedding_length)))
avg_embedding = sum_embedding / embedding_length_smoothed

基本上,这会将所有长度为0的令牌列表的数据都视为长度为1的数据,并避免出现NaN问题。

答案 11 :(得分:0)

我将在这里添加我以前遇到的NaN问题之一。我当时使用了S型函数来激活网络的最后一层。但是,S形激活函数使用指数函数进行计算,我有一些很大的数字输入到S形中。

它导致了无限的渐变,并且一些NaN开始出现。

答案 12 :(得分:0)

我一直在使用Tensorflow Estimator,我认为它可以解决那些除以零和其他数值稳定性问题的情况,偶尔会遇到此错误(ERROR:tensorflow:Model diverged with loss = NaN during training)。多数情况下,这是因为我的输入包括nan。因此:请确保您的输入数据框(或您使用的任何数据框)没有隐藏NaN值。

答案 13 :(得分:0)

另一种选择是使用tf.math.xlogy函数。功能说明说 “如果x == 0,则返回0,否则,按元素返回x * log(y)。” 您可以在这里找到文档:https://www.tensorflow.org/api_docs/python/tf/math/xlogy

答案 14 :(得分:0)

tf.log(y_conv)中,如果y_conv是S型激活函数的输出,则有一种更好的方法来计算tf.log(y_conv)

y_conv = sigmoid(x)。然后,

   log(y_conv) = log(sigmoid(x))
=  log(1 / (1 + exp(-x)))
=  log(1 / (1 + exp(-x))) - x + x =
= -log(1 + exp(-x)) - log(exp(x)) + x =
= -log(1 + exp(x)) + x
=  x - softplus(x)