GNU Radio TensorFlow Blocks

TensorFlow is a powerful python-numpy expression compiler which supports concurrent GPP and GPU offload of large algorithms.  It has been used largely in the machine learning community, but has implications for the rapid and efficient implementation of numerous algorithms in software.   For GNU Radio, it matches up wonderfully with GNU Radio’s python blocks, which pass signal processing data around as numpy ndarrays which can be directly passed to and from TensorFlow compiled functions.   This is very very similar to what I did with gr-theano, but with the caveat that TensorFlow has native complex64 support without any additional patching!  This makes it a great candidate for dropping in highly computationally complex blocks for prototyping and leveraging highly concurrent GPUs when there is gross data parallelism that can easily be leveraged by the compiler.

A quick example of dropping TensorFlow into a python block might look something like this

class add(gr.sync_block):
 x = tensorflow.placeholder("complex64")
 y = tensorflow.placeholder("complex64")
 def __init__(self):
   gr.sync_block.__init__(self,
     name="tf_add",
     in_sig=[numpy.complex64, numpy.complex64],
     out_sig=[numpy.complex64])
   self.sess = tensorflow.Session()
   self.op = tensorflow.add( self.x, self.y)
 def work(self, input_items, output_items):
   rv = self.sess.run([self.op], feed_dict={self.x:input_items[0], self.y:input_items[1]})
   output_items[0][:] = rv[0]
   return len(rv[0])

We simply define self.op as an algorithmic expression we want to compute at run time, and TensorFlow will compile the kernel down to the GPP or GPU depending on available resources, and handle all of the data I/O behind the scenes after we simply pass ndarrays in and out of the work function.

grtfplot

Dropping this block into a new gr-tf out of tree module, we can rapidly plug it into a working GNU Radio flowgraph stream! Clearly there are algorithms which make a lot more sense to offload than “add_cc”.  Things like streaming CAF or MTI computations with lots of concurrent integration come to mind and would be pretty trivial to add.  For now this is just a proof of concept, but it seems like a great way to prototype such things in the future!

The module is available on github @ https://github.com/osh/gr-tf/

Simple Python Bayesian Network Inference with PyOpenPNL

The state of python libraries for performing bayesian graph inference is a bit frustrating.   libpgm is one of the few libraries which seems to exist, but it is quite limited in its abilities.   OpenPNL from Intel is a great c++ implementation of the Matlab Bayes-Net toolbox, but its C++ and Matlab interfaces are both not particularly convenient.   So we set about to properly swig the OpenPNL out to python where it can be used rapidly.   Additionally some of the build infrastructure for OpenPNL was a bit dated and needed some cleaning to work on modern Linux systems.

Our updated modules for both of these can be found at: https://github.com/PyOpenPNL

Not all of OpenPNL has yet been swigged and some of the python interface is still a little bit rough, but it does work.   Here we’ll work through the canonical Bayes-net example from Russell and Norvig, also used in Matlab BNT docs.  The Bayes network of interest is illustrated below.sprinkler

We have a simple graph with four discrete nodes and we would like to instantiate the model, provide evidence and infer marginal probabilities given this evidence.

We focus on the example included in the repo which can be viewed in full here simple_bnet.py

Syntax for defining a DAG’s adjacency matrix and conditional probability distribution types for the Bayes net reads as.

nnodes = 4
# set up the graph
# Dag must be square, with zero diag!
dag = np.zeros([nnodes,nnodes], dtype=np.int32)
dag[0,[1,2]] = 1
dag[2,3] = 1
dag[1,3] = 1
pGraph = openpnl.CGraph.CreateNP(dag)
# set up the node types
types = openpnl.pnlNodeTypeVector()
types.resize(nnodes)
isDiscrete = 1
types[0].SetType( isDiscrete, 2 )
types[1].SetType( isDiscrete, 2 )
types[2].SetType( isDiscrete, 2 )
types[3].SetType( isDiscrete, 2 )
# node associations
nodeAssoc = openpnl.toConstIntVector([0]*nnodes)
# make the bayes net ...
pBNet = openpnl.CBNet.Create( nnodes, types, nodeAssoc, pGraph )

We can verify the DAG structure by plotting with python-networkx, shown below and verifying it matches our goal.

figure_3

In this case we have allocated a Bayes-net with 4 nodes, each with 2 discrete states (T|F).   Next we assign CPDFs to each of the nodes.

pBNet.AllocFactors()
for (node, cpdvals) in [
 (0, [0.5,0.5]),
 (1, [0.8, 0.2, 0.2, 0.8]),
 (2, [0.5, 0.9, 0.5, 0.1]),
 (3, [1, 0.1, 0.1, 0.01, 0, 0.9, 0.9, 0.99]),
 ]:
    parents = pGraph.GetParents(node);
    print "node: ", node, " parents: ", parents
    domain = list(parents) + [node]
    cCPD = openpnl.CTabularCPD.Create( pBNet.GetModelDomain() , openpnl.toConstIntVector(domain) )
    cCPD.AllocMatrix( cpdvals, openpnl.matTable )
    cCPD.NormalizeCPD()
    pBNet.AttachFactor(cCPD)

Assigning these known distributions we now have defined the DAG and the corresponding CPDFs.   We can allocate an inference engine and begin posing problems to it.

We start by assigning evidence that we know Cloudy=False and then seek to measure the marginal of WetGrass resulting.

# Set up the inference engine
infEngine = openpnl.CPearlInfEngine.Create( pBNet );
# Problem 1 P(W|C=0)
evidence = openpnl.mkEvidence( pBNet, [0], [0] )
infEngine.EnterEvidence(evidence)
infEngine.pyMarginalNodes( [3], 0 )
infEngine.GetQueryJPD().Dump()

This provides an output value of [0.788497 0.211503] shown below when plotted.

figure_1

Changing the evidence to Cloudy=True, we can compute this marginal and plot it in comparison.

# Problem 1 P(W|C=1)
evidence = openpnl.mkEvidence( pBNet, [0], [1] )
infEngine.EnterEvidence(evidence)
infEngine.pyMarginalNodes( [3], 0 )
infEngine.GetQueryJPD().Dump()

figure_2

This is an extremely simple BN, but it illustrates the relatively straightforward simplicity with which we can now set up and work with such problems using the PyOpenPNL interface.   Hopefully this project will be of much use to a number of people!   We’ll be largely adding and testing python API support now on an as needed basis.

Installing OpenPNL and PyOpenPNL is now relatively straightforward, I’ve gone through and put together some relatively sane build systems to make these usable, they can be built roughly by following

git clone https://github.com/PyOpenPNL/OpenPNL.git
cd OpenPNL && ./configure && make && sudo make install
git clone https://github.com/PyOpenPNL/PyOpenPNL.git
cd PyOpenPNL && sudo python setup.py build install

This is of course only scratching the surface of BN/PGM style inference.  OpenPNL support DBNs, GMM based continuous distributions, and numerous CPD & Structure learning as well as inference engines under the hood, much more to come soon.

http://inferred.info

GNU Radio Stream Lambda Blocks and Dynamic Runtime Updates

Since adding message based lambda blocks the other day, it seemed an obvious pairing to include a stream equivalent block as well for really rapid prototyping needs. The interface is a bit less simple because we can have N input ports and M output ports, I settled on defining a lambda function which takes a list of input vectors (the same input_items list of numpy arrays normal python blocks get, and an integer output port index). To implement a simplish stream block now that consumes N input streams and produces M output streams, this function prototype simply needs to be implemented. This won’t let you do everything, for instance output streams still need to produce the same number of items, and they are assumed to consume at this same rate. Extending this to a sync decimate or interpolater should be straight forward, but the function prototype would need to become a bit more complex for a “general work” kind of interface which allows producing/consuming each port at different rates, so I chose not to address that for now.

Example Stream Lambda Block  Flowgraph

A simple example graph which demonstrates the use of these blocks is shown below, this graph takes a simple Gaussian noise source in, throttles it to a fixed throughput rate, and then feeds it through a 2-in 2-out stream lambda block. In this case, we provide the following lambda function for the block,

lambda input_items, output_index: (input_items[output_index][::2]**2)*[0.1,0.2][output_index] + [-1,1][output_index]

That is to say:

output_items[0] = input_items[0][::2]**2*0.1 – 1

output_items[1] = input_items[1][::2]**2*0.2 + 1

Here, each stream is independent of the other output, and they could have been implemented as two seperate blocks, but this is just provided as an illustration.

The second block then is a 2-in 1-out stream lambda block which merges the two streams into one output stream, this mapping is given by:

lambda input_items, output_index: (input_items[0][:] * numpy.conj(input_items[1][:]))

This one is a bit simpler, as we will simply have a single evaluation:

output_items[0] = input_items[0]*conj(input_items[1])

The resulting output signal is then shown in the output plot.

stream_lambda_blocks

Dynamic Runtime Updates

What’s really fun is these lambda functions can be updated at runtime to change the block’s functionality. I’ve added a variable_text_input block to gr-pyqt which allows for runtime input of new lambda functions to each block which are passed to the GRC setter callback for each stream lambda block. By running this graph, you can now fiddle with the block’s algorithms at run time and immediately see the output effect on the stream signal. Just don’t type any invalid python at run-time or you’ll crash the block’s thread context.

The dynamic update version of the stream lambda block demo is shown below.

stream_lambda_variable_text

These dynamic function updates should work with the message lambda block as well, but that is not shown here.

The GRC graph for this example can be found @ https://github.com/osh/gr-pyqt/blob/master/apps/test_stream_lambda.grc

Such Samples: A GNU Radio Tool for Rapid Sample Data File Visualization with Message Passing

GNU Radio has always been a great stream processing system for building signal processing systems. Less noticed perhaps is that GNU Radio’s QTGui component matured, it came along with a handful of useful little tools such as gr_time_plot_c which allowed for inspection of sample data files in the time and frequency domain using the same plot widgets we typically use for streaming data. Since this makes use of a handful of GNU Radio stream blocks to perform file reading, sample dropping and alignment, and vector sources to stream data into the stream sink, and we have recently done quite a bit of work with message based plotting, I wondered if it was time to start trying to re-think these a bit. Aside from that, recently I was trying to look at some really large simulated data sets using this tool and getting frustrated with the slugish nature of the tool every time I wanted to move around in time.

So using message passing in GNU Radio I tried to re-think what a sample data analysis tool might look like, and how it might behave responsively to a use interface, accessing only the data of interest and not wasting time reading and trimming large streams using the traditional GNU Radio stream operations on the fixed data file.

Tool Composition

The tool, which I call “Such samples” is actually quite simple when built using GNU Radio Companion. It really only represents two new blocks aside from using the existing gr-pyqt based PDU message plotters, these blocks are:

1. A message based file source, which takes in a tuple message of (start item, item length) and reads out a PDU containing the requested item range after performing a low level fseek to align correctly (never reading in items outside that range).  Reading data is then conducted transparently through numpy’s from_file and pmt’s to_pmt methods which are quite fast and able to handle large chunks.

2. A message based range tool, which allows for a user to manipulate the location and sample range to view within the file. This really just sends out the (start item, item length) tuples to the message file source and doesn’t do much else.

Both of these are initially implemented as pretty concise simple python blocks, but connecting them up in GNU Radio Companion we see that it’s created a pretty simple but nicely performing tool for looking at data files.

The flowgraph is shown below, and could be easily tailored to include all kinds of message operations and plots that one might want to add or include in such a tool.

fg

Tool Operation

The tools is pretty primitive right now, but operation is simple – a command line parameter, or GNU Radio Companion variable default specified which sample data file to open, and by default the sample range [0,4096] is displayed in a time and frequency plot. Moving a slider around allows you to move the start sample throughout the file, and changing the “sample length” field allows you to change the number of complex samples plotted at a time down below. When the file is opened, it tells the range widget the start and end sample within the file so that the bar correctly spans the range of the file.

A screenshot of the tool in operation can be seen below.

plots

Still to do…

There is still lots of clean up remaining, the file source is hard wired for complx64 type samples right now, the gr-pyqt plots still have relatively meaningless labels, no nice context menus, and generally things look a little bit sloppy, but the basic architecture is in place and feels solid and responsive to me. If anyone wants to help with these other items please send pull requests on github!

I would love to switch to using the normal gr-qtgui plotters, once they support message based inputs, but for now these PyQWT based plotters aren’t too bad when passing numpy vectors around in python by reference.

The tool can be found within the gr-pyqt repo’s app’s directory on github @ https://github.com/osh/gr-pyqt/tree/master/apps

A0W2Er7