Repository: keras-team/autokeras

Issue:
Got 'NotImplementedError' on macOS
<!---
**If you are reporting a bug:**
*  Verify that your issue is not being currently addressed by other issues or pull requests.
*  Please note that Auto-Keras is only compatible with **Python 3.6**.
*  Tag the issue with the `bug report` tag.
-->

### Bug Description
`Traceback (most recent call last):
  File "test.py", line 29, in <module>
    clf.fit(x_train, y_train, time_limit=60 * 60)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/autokeras/image/image_supervised.py", line 114, in fit
    super().fit(x, y, time_limit)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/autokeras/supervised.py", line 129, in fit
    self.cnn.fit(self.get_n_output_node(), x_train.shape, train_data, test_data, time_limit)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/autokeras/net_module.py", line 65, in fit
    self.searcher.search(train_data, test_data, int(time_remain))
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/autokeras/search.py", line 200, in search
    generated_other_info, generated_graph = self.generate(remaining_time, q)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/autokeras/search.py", line 251, in generate
    remaining_time, multiprocessing_queue)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/autokeras/bayesian.py", line 350, in generate
    if multiprocessing_queue.qsize() != 0:
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/multiprocessing/queues.py", line 117, in qsize
    return self._maxsize - self._sem._semlock._get_value()
NotImplementedError`

### Reproducing Steps
Just run 'Data with numpy array (.npy) format.' example

### Setup Details
Include the details about the versions of:
 - OS type and version: macOS 10.14.2 
 - Python: 3.6 

### Additional context
seems this is causing the issue: https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Queue.qsize


Discussion:
I have similar errors.
`+----------------------------------------------+
|               Training model 1               |
+----------------------------------------------+
/Users/liuuuche/.virtualenv3/lib/python3.6/site-packages/lightgbm/__init__.py:46: UserWarning: Starting from version 2.2.1, the library file in distribution wheels for macOS is built by the Apple Clang (Xcode_8.3.1) compiler.
This means that in case of installing LightGBM from PyPI via the ``pip install lightgbm`` command, you don't need to install the gcc compiler anymore.
Instead of that, you need to install the OpenMP library, which is required for running LightGBM on the system with the Apple Clang compiler.
You can install the OpenMP library by the following command: ``brew install libomp``.
  "You can install the OpenMP library by the following command: ``brew install libomp``.", UserWarning)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/liuuuche/.virtualenv3/lib/python3.6/site-packages/autokeras/image/image_supervised.py", line 114, in fit
    super().fit(x, y, time_limit)
  File "/Users/liuuuche/.virtualenv3/lib/python3.6/site-packages/autokeras/supervised.py", line 129, in fit
    self.cnn.fit(self.get_n_output_node(), x_train.shape, train_data, test_data, time_limit)
  File "/Users/liuuuche/.virtualenv3/lib/python3.6/site-packages/autokeras/net_module.py", line 65, in fit
    self.searcher.search(train_data, test_data, int(time_remain))
  File "/Users/liuuuche/.virtualenv3/lib/python3.6/site-packages/autokeras/search.py", line 200, in search
    generated_other_info, generated_graph = self.generate(remaining_time, q)
  File "/Users/liuuuche/.virtualenv3/lib/python3.6/site-packages/autokeras/search.py", line 251, in generate
    remaining_time, multiprocessing_queue)
  File "/Users/liuuuche/.virtualenv3/lib/python3.6/site-packages/autokeras/bayesian.py", line 350, in generate
    if multiprocessing_queue.qsize() != 0:
  File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/multiprocessing/queues.py", line 117, in qsize
    return self._maxsize - self._sem._semlock._get_value()
NotImplementedError`
Try brew install libomp.
Install the latest version of Auto-Keras.
If it still doesn't work, please reopen the issue.
Same error.
It seems to be an issue with multiprocessing and macOS.
`multiprocessing/queues.py in qsize(self)`
`def qsize(self):`
`    # Raises NotImplementedError on Mac OSX because of broken sem_getvalue()`
`--> self._maxsize - self._sem._semlock._get_value()`
`def empty(self):`
![captura de pantalla 2019-02-07 a las 17 59 16](https://user-images.githubusercontent.com/2808425/52428381-1d1ed680-2b02-11e9-9076-5fb0bd4e03f2.png)

@boyuangong can you take look into this issue? Thanks

<sub>Sent with <a href="http://githawk.com">GitHawk</a></sub>
Since this is an old problem, I've found a posible solution that other project (lemon) has taken: to subclass the Queue class in order to make it portable. This is the code (comes from https://github.com/vterron/lemon/commit/9ca6b4b1212228dbd4f69b88aaf88b12952d7d6f):

    class SharedCounter(object):
	    """ A synchronized shared counter.
	
	    The locking done by multiprocessing.Value ensures that only a single
	    process or thread may read or write the in-memory ctypes object. However,
	    in order to do n += 1, Python performs a read followed by a write, so a
	    second process may read the old value before the new one is written by the
	    first process. The solution is to use a multiprocessing.Lock to guarantee
	    the atomicity of the modifications to Value.
	
	    This class comes almost entirely from Eli Bendersky's blog:
	    http://eli.thegreenplace.net/2012/01/04/shared-counter-with-pythons-multiprocessing/
	
	    """
	
	    def __init__(self, n = 0):
	        self.count = multiprocessing.Value('i', n)
	
	    def increment(self, n = 1):
	        """ Increment the counter by n (default = 1) """
	        with self.count.get_lock():
	            self.count.value += n
	
	    @property
	    def value(self):
	        """ Return the value of the counter """
	        return self.count.value
	
	
	class Queue(multiprocessing.queues.Queue):
	    """ A portable implementation of multiprocessing.Queue.
	
	    Because of multithreading / multiprocessing semantics, Queue.qsize() may
	    raise the NotImplementedError exception on Unix platforms like Mac OS X
	    where sem_getvalue() is not implemented. This subclass addresses this
	    problem by using a synchronized shared counter (initialized to zero) and
	    increasing / decreasing its value every time the put() and get() methods
	    are called, respectively. This not only prevents NotImplementedError from
	    being raised, but also allows us to implement a reliable version of both
	    qsize() and empty().
	
	    """
	
	    def __init__(self, *args, **kwargs):
	        super(Queue, self).__init__(*args, **kwargs)
	        self.size = SharedCounter(0)
	
	    def put(self, *args, **kwargs):
	        self.size.increment(1)
	        super(Queue, self).put(*args, **kwargs)
	
	    def get(self, *args, **kwargs):
	        self.size.increment(-1)
	        return super(Queue, self).get(*args, **kwargs)
	
	    def qsize(self):
	        """ Reliable implementation of multiprocessing.Queue.qsize() """
	        return self.size.value
	
	    def empty(self):
	        """ Reliable implementation of multiprocessing.Queue.empty() """
	        return not self.qsize()
> Same error.
> It seems to be an issue with multiprocessing and macOS.
> `multiprocessing/queues.py in qsize(self)`
> `def qsize(self):`
> ` # Raises NotImplementedError on Mac OSX because of broken sem_getvalue()`
> `--> self._maxsize - self._sem._semlock._get_value()`
> `def empty(self):`
> ![captura de pantalla 2019-02-07 a las 17 59 16](https://user-images.githubusercontent.com/2808425/52428381-1d1ed680-2b02-11e9-9076-5fb0bd4e03f2.png)

I'm having same problem
> Since this is an old problem, I've found a posible solution that other project (lemon) has taken: to subclass the Queue class in order to make it portable. This is the code (comes from [vterron/lemon@9ca6b4b](https://github.com/vterron/lemon/commit/9ca6b4b1212228dbd4f69b88aaf88b12952d7d6f)):
> 
> ```
> class SharedCounter(object):
>     """ A synchronized shared counter.
> 
>     The locking done by multiprocessing.Value ensures that only a single
>     process or thread may read or write the in-memory ctypes object. However,
>     in order to do n += 1, Python performs a read followed by a write, so a
>     second process may read the old value before the new one is written by the
>     first process. The solution is to use a multiprocessing.Lock to guarantee
>     the atomicity of the modifications to Value.
> 
>     This class comes almost entirely from Eli Bendersky's blog:
>     http://eli.thegreenplace.net/2012/01/04/shared-counter-with-pythons-multiprocessing/
> 
>     """
> 
>     def __init__(self, n = 0):
>         self.count = multiprocessing.Value('i', n)
> 
>     def increment(self, n = 1):
>         """ Increment the counter by n (default = 1) """
>         with self.count.get_lock():
>             self.count.value += n
> 
>     @property
>     def value(self):
>         """ Return the value of the counter """
>         return self.count.value
> 
> 
> class Queue(multiprocessing.queues.Queue):
>     """ A portable implementation of multiprocessing.Queue.
> 
>     Because of multithreading / multiprocessing semantics, Queue.qsize() may
>     raise the NotImplementedError exception on Unix platforms like Mac OS X
>     where sem_getvalue() is not implemented. This subclass addresses this
>     problem by using a synchronized shared counter (initialized to zero) and
>     increasing / decreasing its value every time the put() and get() methods
>     are called, respectively. This not only prevents NotImplementedError from
>     being raised, but also allows us to implement a reliable version of both
>     qsize() and empty().
> 
>     """
> 
>     def __init__(self, *args, **kwargs):
>         super(Queue, self).__init__(*args, **kwargs)
>         self.size = SharedCounter(0)
> 
>     def put(self, *args, **kwargs):
>         self.size.increment(1)
>         super(Queue, self).put(*args, **kwargs)
> 
>     def get(self, *args, **kwargs):
>         self.size.increment(-1)
>         return super(Queue, self).get(*args, **kwargs)
> 
>     def qsize(self):
>         """ Reliable implementation of multiprocessing.Queue.qsize() """
>         return self.size.value
> 
>     def empty(self):
>         """ Reliable implementation of multiprocessing.Queue.empty() """
>         return not self.qsize()
> ```

This worked for me...
Thanks!
Glad it helped!
Did you modified autokeras or subclassed the Queue class in any other way? Maybe the best option would be to PR autokeras.
I did modify autokeras and just used the Queue class mentioned above instead of the multiprocessing.queues.Queue class.
Would you kindly make a Pull Request?
Can anyone help me with implementation of this fix? not really sure what to do

Resolving patch:
diff --git a/autokeras/bayesian.py b/autokeras/bayesian.py
--- a/autokeras/bayesian.py
+++ b/autokeras/bayesian.py
@@ -2,7 +2,6 @@
 import time
 from copy import deepcopy
 from functools import total_ordering
-from multiprocessing.queues import Queue
 from queue import PriorityQueue
 import numpy as np
 import math
@@ -11,6 +10,7 @@
 from scipy.optimize import linear_sum_assignment
 from sklearn.metrics.pairwise import rbf_kernel
 
+from autokeras.custom_queue import Queue
 from autokeras.constant import Constant
 from autokeras.net_transformer import transform
 from autokeras.nn.layers import is_layer, LayerType
diff --git a/autokeras/custom_queue.py b/autokeras/custom_queue.py
new file mode 100644
--- /dev/null
+++ b/autokeras/custom_queue.py
@@ -0,0 +1,64 @@
+from multiprocessing.queues import Queue as MultiQueue
+
+class SharedCounter(object):
+    """ A synchronized shared counter.
+
+    The locking done by multiprocessing.Value ensures that only a single
+    process or thread may read or write the in-memory ctypes object. However,
+    in order to do n += 1, Python performs a read followed by a write, so a
+    second process may read the old value before the new one is written by the
+    first process. The solution is to use a multiprocessing.Lock to guarantee
+    the atomicity of the modifications to Value.
+
+    This class comes almost entirely from Eli Bendersky's blog:
+    http://eli.thegreenplace.net/2012/01/04/shared-counter-with-pythons-multiprocessing/
+
+    """
+
+    def __init__(self, n = 0):
+        self.count = multiprocessing.Value('i', n)
+
+    def increment(self, n = 1):
+        """ Increment the counter by n (default = 1) """
+        with self.count.get_lock():
+            self.count.value += n
+
+    @property
+    def value(self):
+        """ Return the value of the counter """
+        return self.count.value
+
+
+class Queue(MultiQueue):
+    """ A portable implementation of multiprocessing.Queue.
+
+    Because of multithreading / multiprocessing semantics, Queue.qsize() may
+    raise the NotImplementedError exception on Unix platforms like Mac OS X
+    where sem_getvalue() is not implemented. This subclass addresses this
+    problem by using a synchronized shared counter (initialized to zero) and
+    increasing / decreasing its value every time the put() and get() methods
+    are called, respectively. This not only prevents NotImplementedError from
+    being raised, but also allows us to implement a reliable version of both
+    qsize() and empty().
+
+    """
+
+    def __init__(self, *args, **kwargs):
+        super(Queue, self).__init__(*args, **kwargs)
+        self.size = SharedCounter(0)
+
+    def put(self, *args, **kwargs):
+        self.size.increment(1)
+        super(Queue, self).put(*args, **kwargs)
+
+    def get(self, *args, **kwargs):
+        self.size.increment(-1)
+        return super(Queue, self).get(*args, **kwargs)
+
+    def qsize(self):
+        """ Reliable implementation of multiprocessing.Queue.qsize() """
+        return self.size.value
+
+    def empty(self):
+        """ Reliable implementation of multiprocessing.Queue.empty() """
+        return not self.qsize()
diff --git a/nas/greedy.py b/nas/greedy.py
--- a/nas/greedy.py
+++ b/nas/greedy.py
@@ -1,12 +1,11 @@
 import time
 from copy import deepcopy
-from multiprocessing.queues import Queue
 
+from autokeras.custom_queue import Queue
 from autokeras.bayesian import contain, SearchTree
 from autokeras.net_transformer import transform
 from autokeras.search import Searcher
 
-
 class GreedyOptimizer:
 
     def __init__(self, searcher, metric):
@@ -95,4 +94,4 @@ def load_neighbour_best_model(self):
     def get_neighbour_best_model_id(self):
         if self.metric.higher_better():
             return max(self.neighbour_history, key=lambda x: x['metric_value'])['model_id']
-        return min(self.neighbour_history, key=lambda x: x['metric_value'])['model_id']
\ No newline at end of file
+        return min(self.neighbour_history, key=lambda x: x['metric_value'])['model_id']


Test patch: