The beginner’s guide to implementing YOLOv3 in TensorFlow 2.0 (part-3)

Posted by Rahmad Sadli on December 29, 2019 in Deep Learning, Machine Learning, Object Detection, Python Programming

In part 2, we’ve discovered how to construct the YOLOv3 network. In this part 3, we’ll focus on the file yolov3.weights.

So, what we’re going to do in part is to load the weights parameters from the file yolov3.weights, then convert them into the TensorFlow 2.0 weights format. Just to remain you that, the file yolov3.weights contains the pre-trained CNN’s parameters of YOLOv3.

To begin with, let’s take a look at how the YOLOv3 weights are stored.

How YOLOv3’s weights are stored?

The original YOLOv3 weights file yolov3.weights is a binary file and the weights are stored in the float data type.

One thing that we need to know that the weights only belong to convolutional layers. As we know, in YOLOv3, there are 2 convolutional layer types, with and without a batch normalization layer. So, the weights are applied differently for different types of convolutional layers. Since we’re reading the only float data, there’s no clue which one belongs to which layer. If we incorrectly associate these weights with their layers properly, we’ll screw up everything, and the weights won’t be converted. So, that’s why understanding how the weights are stored is crucially important.

Here, I tried to make a simple flowchart in order to describe how the weights are stored.

When we re-write these weights to TensorFlow’s format for a convolutional with a batch normalization layer, we need to switch the position of beta and gamma. So, they’re ordered like this: beta, gamma, means, variance and conv weights. However, the weights’ order remains the same for the convolutional without a batch normalization layer.

All right!!, Now we’re ready to code the weights converter.

Without further ado, let’s do it…

Working with the file convert_weights.py

Open the file convert_weights.py, then copy and paste the following code to the top of it. Here, we import NumPy library and the two functions that we’ve created previously in part 2, YOLOv3Net and parse_cfg.

#convert_weights.py
import numpy as np
from yolov3 import YOLOv3Net
from yolov3 import parse_cfg

Now, let’s create a function called load_weights(). This function has 3 parameters, model, cfgfile, and weightfile. The parameter model is a returning parameters of the network’s model after calling the function YOLOv3Net. Thecfgfile and weightfile are respectively refer to the files yolov3.cfg and yolov3.weights.

def load_weights(model,cfgfile,weightfile):

Open the file yolov3.weights and read the first 5 values. These values are the header information. So, we can skip them all.

    # Open the weights file
    fp = open(weightfile, "rb")

    # The first 5 values are header information
    np.fromfile(fp, dtype=np.int32, count=5)

Then call parse_cfg() function.

    blocks = parse_cfg(cfgfile)

As we did when building the YOLOv3 network, we need to loop over the blocks and search for the convolutional layer. Don’t forget to check whether the convolutional is with batch normalization or not. If it is true, go get the relevant values (gamma, beta, means, and variance), and re-arrange them to the TensorFlow weights order. Otherwise, take the bias values. After that take the convolutional weights and set these weights to the convolutional layer depending on the convolutional type.

    for i, block in enumerate(blocks[1:]):

        if (block["type"] == "convolutional"):
            conv_layer = model.get_layer('conv_' + str(i))
            print("layer: ",i+1,conv_layer)

            filters = conv_layer.filters
            k_size = conv_layer.kernel_size[0]
            in_dim = conv_layer.input_shape[-1]

            if "batch_normalize" in block:

                norm_layer = model.get_layer('bnorm_' + str(i))
                print("layer: ",i+1,norm_layer)
                size = np.prod(norm_layer.get_weights()[0].shape)

                bn_weights = np.fromfile(fp, dtype=np.float32, count=4 * filters)
                # tf [gamma, beta, mean, variance]
                bn_weights = bn_weights.reshape((4, filters))[[1, 0, 2, 3]]

            else:
                conv_bias = np.fromfile(fp, dtype=np.float32, count=filters)

            # darknet shape (out_dim, in_dim, height, width)
            conv_shape = (filters, in_dim, k_size, k_size)
            conv_weights = np.fromfile(
                fp, dtype=np.float32, count=np.product(conv_shape))
            # tf shape (height, width, in_dim, out_dim)
            conv_weights = conv_weights.reshape(
                conv_shape).transpose([2, 3, 1, 0])

            if "batch_normalize" in block:
                norm_layer.set_weights(bn_weights)
                conv_layer.set_weights([conv_weights])
            else:
                conv_layer.set_weights([conv_weights, conv_bias])

Alert if the reading has failed. Then, close the file whether the reading was successful or not.

    assert len(fp.read()) == 0, 'failed to read all data'
    fp.close()

The last part of this code is the main function. Copy and paste the following code of the main function just right after the function load_weights().

def main():

    weightfile = "weights/yolov3.weights"
    cfgfile = "cfg/yolov3.cfg"

    model_size = (416, 416, 3)
    num_classes = 80

    model=YOLOv3Net(cfgfile,model_size,num_classes)
    load_weights(model,cfgfile,weightfile)

    try:
        model.save_weights('weights/yolov3_weights.tf')
        print('\nThe file \'yolov3_weights.tf\' has been saved successfully.')
    except IOError:
        print("Couldn't write the file \'yolov3_weights.tf\'.")

Here is the complete code for the convert_weights.py

#convert_weights.py
import numpy as np
from yolov3 import YOLOv3Net
from yolov3 import parse_cfg

def load_weights(model,cfgfile,weightfile):
    # Open the weights file
    fp = open(weightfile, "rb")

    # Skip 5 header values
    np.fromfile(fp, dtype=np.int32, count=5)

    # The rest of the values are the weights
    blocks = parse_cfg(cfgfile)

    for i, block in enumerate(blocks[1:]):

        if (block["type"] == "convolutional"):
            conv_layer = model.get_layer('conv_' + str(i))
            print("layer: ",i+1,conv_layer)

            filters = conv_layer.filters
            k_size = conv_layer.kernel_size[0]
            in_dim = conv_layer.input_shape[-1]

            if "batch_normalize" in block:

                norm_layer = model.get_layer('bnorm_' + str(i))
                print("layer: ",i+1,norm_layer)
                size = np.prod(norm_layer.get_weights()[0].shape)

                bn_weights = np.fromfile(fp, dtype=np.float32, count=4 * filters)
                # tf [gamma, beta, mean, variance]
                bn_weights = bn_weights.reshape((4, filters))[[1, 0, 2, 3]]

            else:
                conv_bias = np.fromfile(fp, dtype=np.float32, count=filters)

            # darknet shape (out_dim, in_dim, height, width)
            conv_shape = (filters, in_dim, k_size, k_size)
            conv_weights = np.fromfile(
                fp, dtype=np.float32, count=np.product(conv_shape))
            # tf shape (height, width, in_dim, out_dim)
            conv_weights = conv_weights.reshape(
                conv_shape).transpose([2, 3, 1, 0])

            if "batch_normalize" in block:
                norm_layer.set_weights(bn_weights)
                conv_layer.set_weights([conv_weights])
            else:
                conv_layer.set_weights([conv_weights, conv_bias])

    assert len(fp.read()) == 0, 'failed to read all data'
    fp.close()


def main():

    weightfile = "weights/yolov3.weights"
    cfgfile = "cfg/yolov3.cfg"

    model_size = (416, 416, 3)
    num_classes = 80

    model=YOLOv3Net(cfgfile,model_size,num_classes)
    load_weights(model,cfgfile,weightfile)

    try:
        model.save_weights('weights/yolov3_weights.tf')
        print('\nThe file \'yolov3_weights.tf\' has been saved successfully.')
    except IOError:
        print("Couldn't write the file \'yolov3_weights.tf\'.")


if __name__ == '__main__':
    main()

Finally, we can now execute the convert_weights.py file . Open Anaconda prompt or terminal in Pycharm, type the following command and press Enter.

python convert_weights.py

Here is the output, I printed all the convolutional layers just to make sure that the weights are loaded correctly until the last convolutional layer.

If you use PyCharm, look at the Project Navigation on the left side as I pointed with the red boxes, you have 4 new files:

  • checkpoint
  • yolov3_weights.tf.data-00000-of-00002
  • yolov3_weights.tf.data-00001-of-00002
  • yolov3_weights.tf.index

Those files are the TensorFlow 2.0 weights format. So, anytime we want to use them, just simply call them like the only one file, yolov3_weights.tf. We’ll see how to do this in the last part of this tutorial.

This is the end of part 3, and we still have several things to do, those are:

  • creating a pipeline to read the input image or video/camera,
  • computing the prediction,
  • and drawing the bounding boxes prediction over the input image/video/camera.

Those are what we will be doing soon in the last part. Let’s go into it…

Parts

Leave a Reply

Your email address will not be published. All fields are required

What others say

  1. Hi, I have tried to execute the “convert_weights.py” but it gave me the error:
    “can’t multiply sequence by non-int of the type ‘Tensor'” referring to the line: “box_centers = (box_centers + cxy) * strides” in the “yolov3.py”.
    Do you have any suggestion? In my opinion it makes sense that a multiplication between a Tensor and a Tuple is not allowed, but I whould like to know how it is possible to solve it. Thank you.

    • Hi, If you run the code from my Github repo, normally you won’t have this issue.
      Can you please give me detailed info of your system (eg; OS, TensorFlow’s version, anaconda, etc)

  2. I was using Python 3.6 instead of 3.7 as you suggested, I guess this was the problem. Now it works, thank you very much for the support!

  3. I am getting this error when I am trying to use your repo with my customed trained Yolov3:
    line 38, in parse_cfg
    key, value = line.split(“=”)
    ValueError: too many values to unpack (expected 2)

    Can you please help me out?

  4. Hi,
    I am trying to run YOLO on different YOLO dataset other than COCO.
    Can you please let me know what will be the changes in the .weights function if the cfg exists for that dataset

  5. Hi I got this error while running convert_weight.py code

    File “C:\Users\Hitesh\anaconda3\lib\site-packages\tensorflow\python\keras\engine\network.py”, line 522, in get_layer
    raise ValueError(‘No such layer: ‘ + name)

    ValueError: No such layer: conv_0

  6. Hi,I got error
    File “C:\Users\Hitesh\anaconda3\lib\site-packages\tensorflow\python\keras\engine\network.py”, line 522, in get_layer
    raise ValueError(‘No such layer: ‘ + name)

    ValueError: No such layer: conv_0
    how to solved it.Please give me suggestion.

  7. Hi, I have tried to execute the “convert_weights.py” but it gave me the error:
    “can’t multiply sequence by non-int of the type ‘Tensor’” referring to the line: “box_centers = (box_centers + cxy) * strides” in the “yolov3.py”.
    Do you have any suggestion? In my opinion it makes sense that a multiplication between a Tensor and a Tuple is not allowed, but I whould like to know how it is possible to solve it. Thank you.

    • Hi, normally, this code works perfectly using Python 3.7 and Tensorflow 2.0 and 2.1.
      Just take a look at your Python version. Good luck!

  8. Hi,
    I get the following error:
    (py3.7.7_tf2_yolo_venv) D:\Technical\Dev\CV\Object_Detection\YOLO\YOLOv3_TF2>python convert_weights.py
    Traceback (most recent call last):
    File “convert_weights.py”, line 20, in
    from yolov3 import YOLOv3Net
    File “D:\Technical\Dev\CV\Object_Detection\YOLO\YOLOv3_TF2\yolov3.py”, line 25, in
    tf.config.experimental.set_memory_growth(physical_devices[0], True)
    IndexError: list index out of range

    Details of the Environment:
    ****************************
    python = 3.7.7
    opencv = 4.4.0
    tensorflow = 2.1.0

    • Hi, it means you don’t have a GPU card installed on you computer.
      Just make those lines as comments like this:

      “””
      physical_devices = tf.config.experimental.list_physical_devices(‘GPU’)
      assert len(physical_devices) > 0, “Not enough GPU hardware devices available”
      tf.config.experimental.set_memory_growth(physical_devices[0], True)
      “””

  9. Hello, When I am trying to convert the weights after training with my own dataset, I am getting the following error, please help me in resolving this. My input image size is 608 * 608 * 3

    ERROR :
    Traceback (most recent call last):
    File “convert_weights.py”, line 55, in
    main()
    File “convert_weights.py”, line 48, in main
    load_weights(model,cfgfile,weightfile)
    File “convert_weights.py”, line 34, in load_weights
    conv_shape).transpose([2, 3, 1, 0])
    ValueError: cannot reshape array of size 264248 into shape (256,128,3,3)

  10. Hi,
    Firstly, thank you for this blog. This is helpful. I have been reading about YOLOv3 for sometime now. This is the best tutorial I have found. Thank you. Please keep writing.

    I am trying to run this in Spyder(python 3.7).
    I have this error. Please help.

    runfile(‘D:/YOLO/YOLOv3_TF2/convert_weights.py’, wdir=’D:/YOLO/YOLOv3_TF2′)
    Reloaded modules: yolov3
    Traceback (most recent call last):

    File “D:\YOLO\YOLOv3_TF2\convert_weights.py”, line 62, in
    main()

    File “D:\YOLO\YOLOv3_TF2\convert_weights.py”, line 53, in main
    model=YOLOv3Net(cfgfile,model_size,num_classes)

    File “D:\YOLO\YOLOv3_TF2\yolov3.py”, line 22, in YOLOv3Net
    blocks = parse_cfg(cfgfile)

    File “D:\YOLO\YOLOv3_TF2\yolov3.py”, line 17, in parse_cfg
    key, value = line.split(“=”)

    ValueError: not enough values to unpack (expected 2, got 1)

    Thank you,
    Sumana