├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── documentation.md │ └── feature_request.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── LICENSE ├── README.md ├── ROADMAP.md ├── conv_layer.go ├── detections.go ├── example └── yolo-v3 │ └── main.go ├── go.mod ├── go.sum ├── layer.go ├── max_pooling_layer.go ├── route_layer.go ├── shortcut_layer.go ├── test_network_data ├── dog_416x416.jpg ├── download_weights_yolo_tiny_v3.sh ├── download_weights_yolo_v3.sh ├── yolov3-tiny.cfg └── yolov3.cfg ├── test_yolo_op_data ├── 1input.[(10, 13), (16, 30), (33, 23)].npy ├── 1input.[(116, 90), (156, 198), (373, 326)].npy ├── 1input.[(30, 61), (62, 45), (59, 119)].npy ├── 1output.[(10, 13), (16, 30), (33, 23)].npy ├── 1output.[(116, 90), (156, 198), (373, 326)].npy ├── 1output.[(30, 61), (62, 45), (59, 119)].npy ├── test.jpg └── test.txt ├── upsample_layer.go ├── upsample_op.go ├── utils.go ├── weights_darknet.go ├── yolo_diff_op.go ├── yolo_layer.go ├── yolo_op.go ├── yolo_op_test.go ├── yolo_trainer.go └── yolov3.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: LdDl 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior. 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Desktop (please complete the following information):** 23 | - OS version 24 | - Go version 25 | - Gorgonia's core version 26 | - Gorgonia's tensor library version 27 | - Download link for graph file (if graph is small enough provide, you can provide hardcoded graph) 28 | 29 | **Additional context** 30 | Add any other context about the problem here. 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation 3 | about: Question about documentation 4 | title: "[DOCUMENTATION]" 5 | labels: documentation, question 6 | assignees: LdDl 7 | 8 | --- 9 | 10 | **What is your docs question about? Ask it** 11 | A clear and concise description of what the problem is. 12 | 13 | **What do you suggest?* 14 | A clear and concise description of what you want to happen. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE REQUEST]" 5 | labels: enhancement, good first issue 6 | assignees: LdDl 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. 12 | 13 | **Describe the solution you'd like and provide pseudocode examples if you can** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered and provide pseudocode examples if you can** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test_network_data/yolov3-tiny.weights 2 | *.weights -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language; 18 | * Being respectful of differing viewpoints and experiences; 19 | * Gracefully accepting constructive criticism; 20 | * Focusing on what is best for the community; 21 | * Showing empathy towards other community members. 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances; 27 | * Trolling, insulting/derogatory comments, and personal or political attacks; 28 | * Public or private harassment; 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission; 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting. 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at sexykdi@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | We are glad you've decided to make a contribution, because we always need volunteer developers to improve quality of this project. 4 | Our issues page is [here](https://github.com/LdDl/yolo-go/issues) 5 | 6 | # Steps 7 | 1. Fork this project. 8 | 2. Clone to you local workspace. 9 | 3. Pick the issue you would like to participate in. 10 | 4. Work on it. 11 | 5. Update your fork. 12 | 6. Sync with our main branch 13 | ```bash 14 | git remote add upstream https://github.com/LdDl/yolo-go.git 15 | git fetch upstream 16 | git checkout main 17 | git merge upstream/main 18 | ``` 19 | Usage `main` or `master` depends on your enviroment 20 | 21 | 7. Make pull request. Call PR as _[issue-%ISSUE_NUMBER%]_ 22 | 23 | # Important notes 24 | 1. Test coverage is important thing. 25 | 2. ```gofmt``` is necessary. 26 | 3. Code comments are good, but not to many of them. 27 | 4. It is better not to have multiple code-overlapping commits in single PR. 28 | 5. If this library was updated during 29 | 6. Describe the solution in PR comment if it possible. 30 | 31 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # List of contibutors 2 | 3 | # 2020 4 | * LdDL (Dimitrii Lopanov) - https://github.com/LdDl 5 | * morozka - https://github.com/morozka 6 | * Pavel7824 - https://github.com/Pavel7824 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 Dimitrii Lopanov 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WIP. PRs are welcome 2 | Port of [Darknet YOLO](https://github.com/pjreddie/darknet#darknet), but via [Gorgonia](https://github.com/gorgonia/gorgonia) 3 | Both YOLOv3 and tiny-YOLOv3 are implemented. 4 | 5 | # Usage 6 | 7 | Navigate to [example/yolo-v3](example/yolo-v3) folder and run [main.go](example/yolo-v3/main.go). 8 | 9 | Available flags 10 | ```go 11 | go run main.go -h 12 | ``` 13 | ```shell 14 | -cfg string 15 | Path to net configuration file (default "../../test_network_data/yolov3-tiny.cfg") 16 | -image string 17 | Path to image file for 'detector' mode (default "../../test_network_data/dog_416x416.jpg") 18 | -mode string 19 | Choose the mode: detector/training (default "detector") 20 | -train string 21 | Path to folder with labeled data (default "../../test_yolo_op_data") 22 | -weights string 23 | Path to weights file (default "../../test_network_data/yolov3-tiny.weights") 24 | ``` 25 | 26 | For testing tiny-yolov3: 27 | ```shell 28 | go run main.go --mode detector --cfg ../../test_network_data/yolov3-tiny.cfg --weights ../../test_network_data/yolov3-tiny.weights --image ../../test_network_data/dog_416x416.jpg 29 | ``` 30 | 31 | For testing yolov3: 32 | ```shell 33 | go run main.go --mode detector --cfg ../../test_network_data/yolov3.cfg --weights ../../test_network_data/yolov3.weights --image ../../test_network_data/dog_416x416.jpg 34 | ``` 35 | 36 | For training **WIP. PRs are welcome**: 37 | ```shell 38 | go run main.go --mode training --cfg ../../test_network_data/yolov3-tiny.cfg --weights ../../test_network_data/yolov3-tiny.weights --image ../../test_network_data/dog_416x416.jpg --train ../../test_yolo_op_data 39 | ``` 40 | 41 | # Weights and configuration 42 | Weights can be downloaded via curl-scripts [download_weights_yolo_v3.sh](test_network_data/download_weights_yolo_v3.sh) and [download_weights_yolo_tiny_v3.sh](test_network_data/download_weights_yolo_tiny_v3.sh). 43 | Configuration files: [yolov3-tiny.cfg](test_network_data/yolov3-tiny.cfg) and [yolov3.cfg](test_network_data/yolov3.cfg) 44 | 45 | # Network Architecture 46 | ## Tiny-YOLOv3 Architecture is: 47 | ``` 48 | Convolution layer: Filters->16 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 49 | Maxpooling layer: Size->2 Stride->2 50 | Convolution layer: Filters->32 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 51 | Maxpooling layer: Size->2 Stride->2 52 | Convolution layer: Filters->64 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 53 | Maxpooling layer: Size->2 Stride->2 54 | Convolution layer: Filters->128 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 55 | Maxpooling layer: Size->2 Stride->2 56 | Convolution layer: Filters->256 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 57 | Maxpooling layer: Size->2 Stride->2 58 | Convolution layer: Filters->512 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 59 | Maxpooling layer: Size->2 Stride->1 60 | Convolution layer: Filters->1024 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 61 | Convolution layer: Filters->256 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 62 | Convolution layer: Filters->512 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 63 | Convolution layer: Filters->255 Padding->0 Kernel->1x1 Stride->1 Activation->linear Batch->0 Bias->true 64 | YOLO layer: Mask->3 Anchors->[81, 82] | Mask->4 Anchors->[135, 169] | Mask->5 Anchors->[344, 319] 65 | Route layer: Start->13 66 | Convolution layer: Filters->128 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 67 | Upsample layer: Scale->2 68 | Route layer: Start->19 End->8 69 | Convolution layer: Filters->256 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 70 | Convolution layer: Filters->255 Padding->0 Kernel->1x1 Stride->1 Activation->linear Batch->0 Bias->true 71 | YOLO layer: Mask->0 Anchors->[10, 14] | Mask->1 Anchors->[23, 27] | Mask->2 Anchors->[37, 58] 72 | ``` 73 | 74 | ## YOLOv3 Architecture is: 75 | ``` 76 | Convolution layer: Filters->32 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 77 | Convolution layer: Filters->64 Padding->1 Kernel->3x3 Stride->2 Activation->leaky Batch->1 Bias->false 78 | Convolution layer: Filters->32 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 79 | Convolution layer: Filters->64 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 80 | Shortcut layer: index from->1 | index to->3 81 | Convolution layer: Filters->128 Padding->1 Kernel->3x3 Stride->2 Activation->leaky Batch->1 Bias->false 82 | Convolution layer: Filters->64 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 83 | Convolution layer: Filters->128 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 84 | Shortcut layer: index from->5 | index to->7 85 | Convolution layer: Filters->64 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 86 | Convolution layer: Filters->128 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 87 | Shortcut layer: index from->8 | index to->10 88 | Convolution layer: Filters->256 Padding->1 Kernel->3x3 Stride->2 Activation->leaky Batch->1 Bias->false 89 | Convolution layer: Filters->128 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 90 | Convolution layer: Filters->256 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 91 | Shortcut layer: index from->12 | index to->14 92 | Convolution layer: Filters->128 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 93 | Convolution layer: Filters->256 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 94 | Shortcut layer: index from->15 | index to->17 95 | Convolution layer: Filters->128 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 96 | Convolution layer: Filters->256 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 97 | Shortcut layer: index from->18 | index to->20 98 | Convolution layer: Filters->128 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 99 | Convolution layer: Filters->256 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 100 | Shortcut layer: index from->21 | index to->23 101 | Convolution layer: Filters->128 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 102 | Convolution layer: Filters->256 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 103 | Shortcut layer: index from->24 | index to->26 104 | Convolution layer: Filters->128 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 105 | Convolution layer: Filters->256 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 106 | Shortcut layer: index from->27 | index to->29 107 | Convolution layer: Filters->128 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 108 | Convolution layer: Filters->256 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 109 | Shortcut layer: index from->30 | index to->32 110 | Convolution layer: Filters->128 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 111 | Convolution layer: Filters->256 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 112 | Shortcut layer: index from->33 | index to->35 113 | Convolution layer: Filters->512 Padding->1 Kernel->3x3 Stride->2 Activation->leaky Batch->1 Bias->false 114 | Convolution layer: Filters->256 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 115 | Convolution layer: Filters->512 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 116 | Shortcut layer: index from->37 | index to->39 117 | Convolution layer: Filters->256 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 118 | Convolution layer: Filters->512 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 119 | Shortcut layer: index from->40 | index to->42 120 | Convolution layer: Filters->256 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 121 | Convolution layer: Filters->512 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 122 | Shortcut layer: index from->43 | index to->45 123 | Convolution layer: Filters->256 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 124 | Convolution layer: Filters->512 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 125 | Shortcut layer: index from->46 | index to->48 126 | Convolution layer: Filters->256 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 127 | Convolution layer: Filters->512 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 128 | Shortcut layer: index from->49 | index to->51 129 | Convolution layer: Filters->256 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 130 | Convolution layer: Filters->512 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 131 | Shortcut layer: index from->52 | index to->54 132 | Convolution layer: Filters->256 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 133 | Convolution layer: Filters->512 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 134 | Shortcut layer: index from->55 | index to->57 135 | Convolution layer: Filters->256 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 136 | Convolution layer: Filters->512 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 137 | Shortcut layer: index from->58 | index to->60 138 | Convolution layer: Filters->1024 Padding->1 Kernel->3x3 Stride->2 Activation->leaky Batch->1 Bias->false 139 | Convolution layer: Filters->512 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 140 | Convolution layer: Filters->1024 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 141 | Shortcut layer: index from->62 | index to->64 142 | Convolution layer: Filters->512 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 143 | Convolution layer: Filters->1024 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 144 | Shortcut layer: index from->65 | index to->67 145 | Convolution layer: Filters->512 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 146 | Convolution layer: Filters->1024 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 147 | Shortcut layer: index from->68 | index to->70 148 | Convolution layer: Filters->512 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 149 | Convolution layer: Filters->1024 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 150 | Shortcut layer: index from->71 | index to->73 151 | Convolution layer: Filters->512 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 152 | Convolution layer: Filters->1024 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 153 | Convolution layer: Filters->512 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 154 | Convolution layer: Filters->1024 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 155 | Convolution layer: Filters->512 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 156 | Convolution layer: Filters->1024 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 157 | Convolution layer: Filters->255 Padding->0 Kernel->1x1 Stride->1 Activation->linear Batch->0 Bias->true 158 | YOLO layer: Mask->6 Anchors->[116, 90] | Mask->7 Anchors->[156, 198] | Mask->8 Anchors->[373, 326] 159 | Route layer: Start->79 160 | Convolution layer: Filters->256 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 161 | Upsample layer: Scale->2 162 | Route layer: Start->85 End->61 163 | Convolution layer: Filters->256 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 164 | Convolution layer: Filters->512 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 165 | Convolution layer: Filters->256 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 166 | Convolution layer: Filters->512 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 167 | Convolution layer: Filters->256 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 168 | Convolution layer: Filters->512 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 169 | Convolution layer: Filters->255 Padding->0 Kernel->1x1 Stride->1 Activation->linear Batch->0 Bias->true 170 | YOLO layer: Mask->3 Anchors->[30, 61] | Mask->4 Anchors->[62, 45] | Mask->5 Anchors->[59, 119] 171 | Route layer: Start->91 172 | Convolution layer: Filters->128 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 173 | Upsample layer: Scale->2 174 | Route layer: Start->97 End->36 175 | Convolution layer: Filters->128 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 176 | Convolution layer: Filters->256 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 177 | Convolution layer: Filters->128 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 178 | Convolution layer: Filters->256 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 179 | Convolution layer: Filters->128 Padding->0 Kernel->1x1 Stride->1 Activation->leaky Batch->1 Bias->false 180 | Convolution layer: Filters->256 Padding->1 Kernel->3x3 Stride->1 Activation->leaky Batch->1 Bias->false 181 | Convolution layer: Filters->255 Padding->0 Kernel->1x1 Stride->1 Activation->linear Batch->0 Bias->true 182 | YOLO layer: Mask->0 Anchors->[10, 13] | Mask->1 Anchors->[16, 30] | Mask->2 Anchors->[33, 23] 183 | ``` -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # ROADMAP 2 | 3 | New ideas, thought about needed features will be stored in this file. 4 | 5 | - [x] Darknet YOLO v3 layers 6 | - [x] Convolution layer; 7 | - [x] Maxpool layer; 8 | - [x] Upsample layer; 9 | - [x] Route layer; 10 | - [x] Shortcut layer; 11 | - [x] YOLO layer; 12 | - [x] Utilities 13 | - [x] Darknet-based weights parser 14 | - [x] Darknet-based neural network configurtion file parser 15 | - [x] NMS for detected objects and all corresponding functions such as IOU, bbox rectifying 16 | - [x] Convert image to slice of float32 17 | - [x] Resize image and average color picker 18 | - [x] Pixel extracting and adapting it for []float32 19 | - [x] New Gorgonia operations (or modified existing ones): 20 | - [x] Upsample and its derivative 21 | - [x] YOLO and its derivative 22 | - [ ] Test coverage (Its always good) 23 | - [x] Full inference example 24 | - [x] Training **WIP** 25 | - [ ] Loss function **WIP, PRs are welcome** 26 | - [ ] Proper backpropagation **WIP, PRs are welcome** 27 | - [ ] Optimizations (replace 'raw loop' in code with Gorgonia core functions) 28 | - [ ] GPU (Here we have much work to do) 29 | - [ ] Instructions 30 | - [ ] Code itself (with Gorgonia CUDA-based library) 31 | - [ ] Benchmarks 32 | - [ ] Documentation (fill [README.md](README.md) with usefull stuff) **WIP** 33 | 34 | Updated at: 2020-10-28 -------------------------------------------------------------------------------- /conv_layer.go: -------------------------------------------------------------------------------- 1 | package yologo 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pkg/errors" 7 | "gorgonia.org/gorgonia" 8 | "gorgonia.org/tensor" 9 | ) 10 | 11 | type convLayer struct { 12 | filters int 13 | padding int 14 | kernelSize int 15 | stride int 16 | activation string 17 | activationReLUCoef float64 18 | batchNormalize int 19 | bias bool 20 | biases []float32 21 | 22 | layerIndex int 23 | 24 | convNode *gorgonia.Node 25 | biasNode *gorgonia.Node 26 | } 27 | 28 | func (l *convLayer) String() string { 29 | return fmt.Sprintf( 30 | "Convolution layer: Filters->%[1]d Padding->%[2]d Kernel->%[3]dx%[3]d Stride->%[4]d Activation->%[5]s Batch->%[6]d Bias->%[7]t", 31 | l.filters, l.padding, l.kernelSize, l.stride, l.activation, l.batchNormalize, l.bias, 32 | ) 33 | } 34 | 35 | func (l *convLayer) Type() string { 36 | return "convolutional" 37 | } 38 | 39 | func (l *convLayer) ToNode(g *gorgonia.ExprGraph, inputs ...*gorgonia.Node) (*gorgonia.Node, error) { 40 | 41 | // Prepae Conv2D operation 42 | convOut, err := gorgonia.Conv2d(inputs[0], l.convNode, tensor.Shape{l.kernelSize, l.kernelSize}, []int{l.padding, l.padding}, []int{l.stride, l.stride}, []int{1, 1}) 43 | if err != nil { 44 | return &gorgonia.Node{}, errors.Wrap(err, "Can't prepare convolution operation") 45 | } 46 | 47 | // Prepare biases 48 | shp := convOut.Shape() 49 | iters := shp.TotalSize() / len(l.biases) 50 | dataF32 := []float32{} 51 | for b := 0; b < len(l.biases); b++ { 52 | for j := 0; j < iters; j++ { 53 | dataF32 = append(dataF32, l.biases[b]) 54 | } 55 | } 56 | biasTensor := tensor.New(tensor.WithBacking(dataF32), tensor.WithShape(shp...)) 57 | l.biasNode = gorgonia.NewTensor(g, tensor.Float32, 4, gorgonia.WithShape(shp...), gorgonia.WithName(fmt.Sprintf("bias_%d", l.layerIndex)), gorgonia.WithValue(biasTensor)) 58 | biasOut, err := gorgonia.Add(convOut, l.biasNode) 59 | if err != nil { 60 | return &gorgonia.Node{}, errors.Wrap(err, "Can't prepare bias add operation") 61 | } 62 | 63 | // If activation is needed then apply LeakyReLU operation 64 | if l.activation == "leaky" { 65 | activationOut, err := gorgonia.LeakyRelu(biasOut, l.activationReLUCoef) 66 | if err != nil { 67 | return &gorgonia.Node{}, errors.Wrap(err, "Can't prepare activation operation") 68 | } 69 | return activationOut, nil 70 | } 71 | return convOut, nil 72 | } 73 | -------------------------------------------------------------------------------- /detections.go: -------------------------------------------------------------------------------- 1 | package yologo 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "sort" 7 | 8 | "gorgonia.org/tensor" 9 | ) 10 | 11 | // DetectionRectangle Representation of detection 12 | type DetectionRectangle struct { 13 | conf float32 14 | rect image.Rectangle 15 | class string 16 | score float32 17 | } 18 | 19 | func (dr *DetectionRectangle) String() string { 20 | return fmt.Sprintf("Detection:\n\tClass = %s\n\tScore = %f\n\tConfidence = %f\n\tCoordinates: [RightTopX = %d, RightTopY = %d, LeftBottomX = %d, LeftBottomY = %d]", 21 | dr.class, dr.score, dr.conf, dr.rect.Min.X, dr.rect.Min.Y, dr.rect.Max.X, dr.rect.Max.Y, 22 | ) 23 | } 24 | 25 | // GetClass Returns class of object 26 | func (dr *DetectionRectangle) GetClass() string { 27 | return dr.class 28 | } 29 | 30 | // Detections Detection rectangles 31 | type Detections []*DetectionRectangle 32 | 33 | /* Methods to match sort.Interface interface */ 34 | func (detections Detections) Len() int { return len(detections) } 35 | func (detections Detections) Swap(i, j int) { 36 | detections[i], detections[j] = detections[j], detections[i] 37 | } 38 | func (detections Detections) Less(i, j int) bool { return detections[i].conf < detections[j].conf } 39 | 40 | // DetectionsOrder Ordering for X-axis 41 | type DetectionsOrder []*DetectionRectangle 42 | 43 | /* Methods to match sort.Interface interface */ 44 | func (detections DetectionsOrder) Len() int { return len(detections) } 45 | func (detections DetectionsOrder) Swap(i, j int) { 46 | detections[i], detections[j] = detections[j], detections[i] 47 | } 48 | func (detections DetectionsOrder) Less(i, j int) bool { 49 | return detections[i].rect.Min.X < detections[j].rect.Min.X 50 | } 51 | 52 | // ProcessOutput Returns postprocessed detections 53 | func (net *YOLOv3) ProcessOutput(classes []string, scoreTreshold, iouTreshold float32) (Detections, error) { 54 | if len(classes) != net.classesNum { 55 | return nil, fmt.Errorf("length of provided slice of classes is not equal to YOLO network 'classesNum' field") 56 | } 57 | preparedDetections := make(Detections, 0) 58 | out := net.GetOutput() 59 | for i := range out { 60 | nodeValue := out[i].Value() 61 | var tensorValue tensor.Tensor 62 | switch nodeValue.(type) { 63 | case tensor.Tensor: 64 | tensorValue = nodeValue.(tensor.Tensor) 65 | break 66 | default: 67 | fmt.Printf("Warning: YOLO output node #%d should be type of tensor.Tensor", i) 68 | break 69 | } 70 | 71 | dataValue := tensorValue.Data() 72 | dataF32 := make([]float32, 0) 73 | switch dataValue.(type) { 74 | case []float32: 75 | dataF32 = dataValue.([]float32) 76 | break 77 | default: 78 | fmt.Printf("Warning: YOLO output tensor #%d should be type of []float32", i) 79 | break 80 | } 81 | 82 | detections := prepareDetections(dataF32, scoreTreshold, net.netSize, classes) 83 | preparedDetections = append(preparedDetections, detections...) 84 | } 85 | 86 | finalDetections := nonMaxSupr(preparedDetections, iouTreshold) 87 | sort.Sort(DetectionsOrder(finalDetections)) 88 | return finalDetections, nil 89 | } 90 | 91 | // prepareDetections Filter detections 92 | func prepareDetections(data []float32, scoreTreshold float32, netSize int, classes []string) Detections { 93 | detections := make(Detections, 0) 94 | for i := 0; i < len(data); i += (len(classes) + 5) { 95 | class := 0 96 | maxProbability := float32(0.0) 97 | for j := 5; j < 5+len(classes); j++ { 98 | if data[i+j] > maxProbability { 99 | maxProbability = data[i+j] 100 | class = (j - 5) % len(classes) 101 | } 102 | } 103 | if maxProbability*data[i+4] > scoreTreshold { 104 | box := &DetectionRectangle{ 105 | conf: data[i+4], 106 | rect: Rectify(int(data[i]), int(data[i+1]), int(data[i+2]), int(data[i+3]), netSize, netSize), 107 | class: classes[class], 108 | score: maxProbability, 109 | } 110 | detections = append(detections, box) 111 | } 112 | } 113 | return detections 114 | } 115 | 116 | // nonMaxSupr Sorts boxes by confidence 117 | func nonMaxSupr(detections Detections, iouTreshold float32) Detections { 118 | sort.Sort(detections) 119 | nms := make(Detections, 0) 120 | if len(detections) == 0 { 121 | return nms 122 | } 123 | nms = append(nms, detections[0]) 124 | for i := 1; i < len(detections); i++ { 125 | tocheck, del := len(nms), false 126 | for j := 0; j < tocheck; j++ { 127 | currIOU := IOUFloat32(detections[i].rect, nms[j].rect) 128 | if currIOU > iouTreshold && detections[i].class == nms[j].class { 129 | del = true 130 | break 131 | } 132 | } 133 | if !del { 134 | nms = append(nms, detections[i]) 135 | } 136 | } 137 | return nms 138 | } 139 | -------------------------------------------------------------------------------- /example/yolo-v3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "path/filepath" 8 | "strconv" 9 | "strings" 10 | "time" 11 | 12 | yologo "github.com/LdDl/yolo-go" 13 | "gorgonia.org/gorgonia" 14 | "gorgonia.org/tensor" 15 | ) 16 | 17 | var ( 18 | imgWidth = 416 19 | imgHeight = 416 20 | channels = 3 21 | boxes = 3 22 | leakyCoef = 0.1 23 | 24 | modeStr = flag.String("mode", "detector", "Choose the mode: detector/training") 25 | weights = flag.String("weights", "../../test_network_data/yolov3-tiny.weights", "Path to weights file") 26 | cfg = flag.String("cfg", "../../test_network_data/yolov3-tiny.cfg", "Path to net configuration file") 27 | imagePath = flag.String("image", "../../test_network_data/dog_416x416.jpg", "Path to image file for 'detector' mode") 28 | trainingFolder = flag.String("train", "../../test_yolo_op_data", "Path to folder with labeled data") 29 | 30 | cocoClasses = []string{"person", "bicycle", "car", "motorbike", "aeroplane", "bus", "train", "truck", "boat", "traffic light", "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard", "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple", "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "sofa", "pottedplant", "bed", "diningtable", "toilet", "tvmonitor", "laptop", "mouse", "remote", "keyboard", "cell phone", "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear", "hair drier", "toothbrush"} 31 | scoreThreshold = float32(0.8) 32 | iouThreshold = float32(0.3) 33 | ) 34 | 35 | func main() { 36 | // Parse flags 37 | flag.Parse() 38 | 39 | // Create new graph 40 | g := gorgonia.NewGraph() 41 | 42 | // Prepare input tensor 43 | input := gorgonia.NewTensor(g, tensor.Float32, 4, gorgonia.WithShape(1, channels, imgWidth, imgHeight), gorgonia.WithName("input")) 44 | 45 | // Prepare YOLOv3 tiny vartiation 46 | model, err := yologo.NewYoloV3(g, input, len(cocoClasses), boxes, leakyCoef, *cfg, *weights) 47 | if err != nil { 48 | fmt.Printf("Can't prepare tiny-YOLOv3 network due the error: %s\n", err.Error()) 49 | return 50 | } 51 | model.Print() 52 | 53 | switch strings.ToLower(*modeStr) { 54 | case "detector": 55 | // Parse image file as []float32 56 | imgf32, err := yologo.GetFloat32Image(*imagePath, imgHeight, imgWidth) 57 | if err != nil { 58 | fmt.Printf("Can't read []float32 from image due the error: %s\n", err.Error()) 59 | return 60 | } 61 | 62 | // Prepare image tensor 63 | image := tensor.New(tensor.WithShape(1, channels, imgHeight, imgWidth), tensor.Of(tensor.Float32), tensor.WithBacking(imgf32)) 64 | 65 | // Fill input tensor with data from image tensor 66 | err = gorgonia.Let(input, image) 67 | if err != nil { 68 | fmt.Printf("Can't let input = []float32 due the error: %s\n", err.Error()) 69 | return 70 | } 71 | 72 | // Prepare new Tape machine 73 | tm := gorgonia.NewTapeMachine(g) 74 | defer tm.Close() 75 | 76 | // Do forward path through the neural network (YOLO) 77 | st := time.Now() 78 | if err := tm.RunAll(); err != nil { 79 | fmt.Printf("Can't run tape machine due the error: %s\n", err.Error()) 80 | return 81 | } 82 | fmt.Println("Feedforwarded in:", time.Since(st)) 83 | 84 | // Do not forget to reset Tape machine (usefully when doing RunAll() in a loop) 85 | tm.Reset() 86 | 87 | // Postprocessing YOLO's output 88 | st = time.Now() 89 | dets, err := model.ProcessOutput(cocoClasses, scoreThreshold, iouThreshold) 90 | if err != nil { 91 | fmt.Printf("Can't do postprocessing due error: %s", err.Error()) 92 | return 93 | } 94 | fmt.Println("Postprocessed in:", time.Since(st)) 95 | 96 | fmt.Println("Detections:") 97 | for i := range dets { 98 | fmt.Println(dets[i]) 99 | } 100 | 101 | break 102 | case "training": 103 | // Prepare training data 104 | labeledData, err := parseFolder(*trainingFolder) 105 | if err != nil { 106 | fmt.Printf("Can't prepare labeled data due the error: %s\n", err.Error()) 107 | return 108 | } 109 | err = model.ActivateTrainingMode() 110 | if err != nil { 111 | fmt.Printf("Can't activate training mode due the error: %s\n", err.Error()) 112 | return 113 | } 114 | 115 | // Init solver and concat YOLO output 116 | solver := gorgonia.NewRMSPropSolver(gorgonia.WithLearnRate(0.00001)) 117 | modelOut := model.GetOutput() 118 | concatOut, err := gorgonia.Concat(1, modelOut...) 119 | if err != nil { 120 | fmt.Printf("Can't concatenate YOLO layers outputs in Training mode due the error: %s\n", err.Error()) 121 | return 122 | } 123 | 124 | // Evaluate costs 125 | costs, err := gorgonia.Sum(concatOut, 0, 1, 2) 126 | if err != nil { 127 | fmt.Printf("Can't evaluate costs in Training mode due the error: %s\n", err.Error()) 128 | return 129 | } 130 | 131 | // Evaluate gradients 132 | _, err = gorgonia.Grad(costs, model.LearningNodes...) 133 | if err != nil { 134 | fmt.Printf("Can't evaluate gradients in Training mode due the error: %s\n", err.Error()) 135 | return 136 | } 137 | prog, locMap, err := gorgonia.Compile(g) 138 | if err != nil { 139 | fmt.Printf("Can't compile graph in Training mode due the error: %s\n", err.Error()) 140 | return 141 | } 142 | 143 | // Prepare new Tape machine 144 | tm := gorgonia.NewTapeMachine(g, gorgonia.WithPrecompiled(prog, locMap), gorgonia.BindDualValues(model.LearningNodes...)) 145 | defer tm.Close() 146 | 147 | iter := 0 148 | for i := range labeledData { 149 | // Parse image file as []float32 150 | filePath := fmt.Sprintf("%s/%s.jpg", *trainingFolder, i) 151 | imgf32, err := yologo.GetFloat32Image(filePath, imgHeight, imgWidth) 152 | if err != nil { 153 | fmt.Printf("Can't read []float32 from image due the error: %s\n", err.Error()) 154 | return 155 | } 156 | 157 | // Set desired target on current step 158 | err = model.SetTarget(labeledData[i]) 159 | if err != nil { 160 | fmt.Printf("Can't set []float32 as target due the error: %s\n", err.Error()) 161 | return 162 | } 163 | 164 | // Prepare image tensor 165 | image := tensor.New(tensor.WithShape(1, channels, imgHeight, imgWidth), tensor.Of(tensor.Float32), tensor.WithBacking(imgf32)) 166 | 167 | // Fill input tensor with data from image tensor 168 | err = gorgonia.Let(input, image) 169 | if err != nil { 170 | fmt.Printf("Can't let input = []float32 due the error: %s\n", err.Error()) 171 | return 172 | } 173 | 174 | // Do training step 175 | st := time.Now() 176 | if err := tm.RunAll(); err != nil { 177 | fmt.Printf("Can't run tape machine due the error: %s\n", err.Error()) 178 | return 179 | } 180 | // Reduce learning rate with more iteration steps 181 | if iter == 15 { 182 | solver = gorgonia.NewRMSPropSolver(gorgonia.WithLearnRate(0.000001)) 183 | } 184 | if iter == 150 { 185 | solver = gorgonia.NewRMSPropSolver(gorgonia.WithLearnRate(0.0000001)) 186 | } 187 | fmt.Printf("Training iteration #%d done in: %v\n", iter, time.Since(st)) 188 | fmt.Printf("\tCurrent costs are: %v\n", costs.Value()) 189 | err = solver.Step(gorgonia.NodesToValueGrads(model.LearningNodes)) 190 | if err != nil { 191 | fmt.Printf("Can't do solver.Step() in Training mode due the error: %s\n", err.Error()) 192 | } 193 | 194 | // Do not forget to reset Tape machine on each step 195 | tm.Reset() 196 | iter++ 197 | } 198 | break 199 | default: 200 | fmt.Printf("Mode '%s' is not implemented", *modeStr) 201 | return 202 | } 203 | 204 | } 205 | 206 | func parseFolder(dir string) (map[string][]float32, error) { 207 | filesInfo, err := ioutil.ReadDir(dir) 208 | if err != nil { 209 | return nil, err 210 | } 211 | targets := map[string][]float32{} 212 | 213 | for i := range filesInfo { 214 | sliceOfF32 := []float32{} 215 | fileInfo := filesInfo[i] 216 | // Parse only *.txt files 217 | if fileInfo.IsDir() || filepath.Ext(fileInfo.Name()) != ".txt" { 218 | continue 219 | } 220 | filePath := fmt.Sprintf("%s/%s", dir, fileInfo.Name()) 221 | fileBytes, err := ioutil.ReadFile(filePath) 222 | if err != nil { 223 | return nil, err 224 | } 225 | fileContentAsArray := strings.Split(strings.ReplaceAll(string(fileBytes), "\n", " "), " ") 226 | for j := range fileContentAsArray { 227 | entity := strings.TrimSpace(fileContentAsArray[j]) 228 | if entity == "" { 229 | continue 230 | } 231 | entityF32, err := strconv.ParseFloat(entity, 32) 232 | if err != nil { 233 | return nil, err 234 | } 235 | sliceOfF32 = append(sliceOfF32, float32(entityF32)) 236 | } 237 | targets[strings.Split(fileInfo.Name(), ".")[0]] = sliceOfF32 238 | } 239 | 240 | if len(targets) == 0 { 241 | return nil, fmt.Errorf("Folder '%s' doesn't contain any *.txt files (annotation files for YOLO)", dir) 242 | } 243 | 244 | return targets, nil 245 | } 246 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/LdDl/yolo-go 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/apache/arrow/go/arrow v0.0.0-20201221100236-9724afd73248 // indirect 7 | github.com/awalterschulze/gographviz v2.0.3+incompatible // indirect 8 | github.com/chewxy/hm v1.0.0 9 | github.com/chewxy/math32 v1.0.6 10 | github.com/davecgh/go-spew v1.1.1 // indirect 11 | github.com/gogo/protobuf v1.3.1 // indirect 12 | github.com/golang/protobuf v1.4.3 // indirect 13 | github.com/google/flatbuffers v1.12.0 // indirect 14 | github.com/google/go-cmp v0.5.4 // indirect 15 | github.com/kr/text v0.2.0 // indirect 16 | github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353 // indirect 17 | github.com/pkg/errors v0.9.1 18 | github.com/stretchr/testify v1.6.1 19 | golang.org/x/exp v0.0.0-20201221025956-e89b829e73ea // indirect 20 | golang.org/x/net v0.0.0-20201216054612-986b41b23924 // indirect 21 | golang.org/x/sys v0.0.0-20201221093633-bc327ba9c2f0 // indirect 22 | golang.org/x/text v0.3.4 // indirect 23 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 24 | gonum.org/v1/gonum v0.8.2 // indirect 25 | gonum.org/v1/netlib v0.0.0-20201012070519-2390d26c3658 // indirect 26 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d // indirect 27 | google.golang.org/grpc v1.34.0 // indirect 28 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 29 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect 30 | gorgonia.org/gorgonia v0.9.15 31 | gorgonia.org/tensor v0.9.14 32 | ) 33 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 5 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 6 | github.com/apache/arrow/go/arrow v0.0.0-20200909005831-30143fc493df h1:iXnL0pMIR/RDUWl0kCbc0CQ3UyehlyV+t/DYCLJTbFc= 7 | github.com/apache/arrow/go/arrow v0.0.0-20200909005831-30143fc493df/go.mod h1:QNYViu/X0HXDHw7m3KXzWSVXIbfUvJqBFe6Gj8/pYA0= 8 | github.com/apache/arrow/go/arrow v0.0.0-20201221100236-9724afd73248 h1:ASXeluNDuWdRTWxGu5SqvZVMelJ9Zzxa1Ha6ty/+lGQ= 9 | github.com/apache/arrow/go/arrow v0.0.0-20201221100236-9724afd73248/go.mod h1:c9sxoIT3YgLxH4UhLOCKaBlEojuMhVYpk4Ntv3opUTQ= 10 | github.com/awalterschulze/gographviz v0.0.0-20190221210632-1e9ccb565bca h1:xwIXr1FpA2XBoohlpvgb11No/zbsh5Clm/98PWPcHVA= 11 | github.com/awalterschulze/gographviz v0.0.0-20190221210632-1e9ccb565bca/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs= 12 | github.com/awalterschulze/gographviz v2.0.3+incompatible h1:9sVEXJBJLwGX7EQVhLm2elIKCm7P2YHFC8v6096G09E= 13 | github.com/awalterschulze/gographviz v2.0.3+incompatible/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs= 14 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 15 | github.com/chewxy/hm v1.0.0 h1:zy/TSv3LV2nD3dwUEQL2VhXeoXbb9QkpmdRAVUFiA6k= 16 | github.com/chewxy/hm v1.0.0/go.mod h1:qg9YI4q6Fkj/whwHR1D+bOGeF7SniIP40VweVepLjg0= 17 | github.com/chewxy/math32 v1.0.0/go.mod h1:Miac6hA1ohdDUTagnvJy/q+aNnEk16qWUdb8ZVhvCN0= 18 | github.com/chewxy/math32 v1.0.6 h1:JWZYUNl2rtgVVui6z8JBsDgkOG2DYmfSODyo95yKfx4= 19 | github.com/chewxy/math32 v1.0.6/go.mod h1:dOB2rcuFrCn6UHrze36WSLVPKtzPMRAQvBvUwkSsLqs= 20 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 21 | github.com/cloudflare/cfssl v0.0.0-20190808011637-b1ec8c586c2a/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= 22 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 23 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 24 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 25 | github.com/cznic/cc v0.0.0-20181122101902-d673e9b70d4d/go.mod h1:m3fD/V+XTB35Kh9zw6dzjMY+We0Q7PMf6LLIC4vuG9k= 26 | github.com/cznic/golex v0.0.0-20181122101858-9c343928389c/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc= 27 | github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= 28 | github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc= 29 | github.com/cznic/xc v0.0.0-20181122101856-45b06973881e/go.mod h1:3oFoiOvCDBYH+swwf5+k/woVmWy7h1Fcyu8Qig/jjX0= 30 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 31 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 32 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 33 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 34 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 35 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 36 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 37 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= 38 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 39 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 40 | github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 41 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 42 | github.com/go-gota/gota v0.10.1 h1:BWci+R5dE28GnXoD1EWoQqe7WCQHAPJ996mK7LZrB4U= 43 | github.com/go-gota/gota v0.10.1/go.mod h1:NZLQccXn0rABmkXjsaugRY6l+UH2dDZSgIgF8E2ipmA= 44 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 45 | github.com/gogo/protobuf v1.3.0 h1:G8O7TerXerS4F6sx9OV7/nRfJdnXgHZu/S/7F2SN+UE= 46 | github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 47 | github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= 48 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 49 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 50 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 51 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 52 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 53 | github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= 54 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 55 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 56 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 57 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 58 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 59 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 60 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 61 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 62 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 63 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= 64 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 65 | github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= 66 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 67 | github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc= 68 | github.com/google/flatbuffers v1.10.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 69 | github.com/google/flatbuffers v1.11.0 h1:O7CEyB8Cb3/DmtxODGtLHcEvpr81Jm5qLg/hsHnxA2A= 70 | github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 71 | github.com/google/flatbuffers v1.12.0 h1:/PtAHvnBY4Kqnx/xCQ3OIV9uYcSFGScBsWI3Oogeh6w= 72 | github.com/google/flatbuffers v1.12.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 73 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 74 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 75 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 76 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 77 | github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= 78 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 79 | github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= 80 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 81 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 82 | github.com/gorgonia/bindgen v0.0.0-20180812032444-09626750019e/go.mod h1:YzKk63P9jQHkwAo2rXHBv02yPxDzoQT2cBV0x5bGV/8= 83 | github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 84 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 85 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 86 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 87 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 88 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 89 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 90 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 91 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 92 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 93 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 94 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 95 | github.com/leesper/go_rng v0.0.0-20171009123644-5344a9259b21 h1:O75p5GUdUfhJqNCMM1ntthjtJCOHVa1lzMSfh5Qsa0Y= 96 | github.com/leesper/go_rng v0.0.0-20171009123644-5344a9259b21/go.mod h1:N0SVk0uhy+E1PZ3C9ctsPRlvOPAFPkCNlcPBDkt0N3U= 97 | github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353 h1:X/79QL0b4YJVO5+OsPH9rF2u428CIrGL/jLmPsoOQQ4= 98 | github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353/go.mod h1:N0SVk0uhy+E1PZ3C9ctsPRlvOPAFPkCNlcPBDkt0N3U= 99 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 100 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 101 | github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= 102 | github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 103 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= 104 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 105 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 106 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 107 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 108 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 109 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 110 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 111 | github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 112 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 113 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 114 | github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 115 | github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 116 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 117 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 118 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 119 | github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho= 120 | github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 121 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 122 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 123 | github.com/xtgo/set v1.0.0 h1:6BCNBRv3ORNDQ7fyoJXRv+tstJz3m1JVFQErfeZz2pY= 124 | github.com/xtgo/set v1.0.0/go.mod h1:d3NHzGzSa0NmB2NhFyECA+QdRp29oEn2xbT+TpeFoM8= 125 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 126 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 127 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 128 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 129 | golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 130 | golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 131 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 132 | golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 133 | golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495 h1:I6A9Ag9FpEKOjcKrRNjQkPHawoXIhKyTGfvvjFAiiAk= 134 | golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 135 | golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= 136 | golang.org/x/exp v0.0.0-20201221025956-e89b829e73ea h1:GnGfrp0fiNhiBS/v/aCFTmfEWgkvxW4Qiu8oM2/IfZ4= 137 | golang.org/x/exp v0.0.0-20201221025956-e89b829e73ea/go.mod h1:I6l2HNBLBZEcrOoCpyKLdY2lHoRZ8lI4x60KMCQDft4= 138 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 139 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 140 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 141 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 142 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 143 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0= 144 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 145 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 146 | golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= 147 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 148 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 149 | golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 150 | golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 151 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 152 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 153 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 154 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 155 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 156 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 157 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 158 | golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= 159 | golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 160 | golang.org/x/net v0.0.0-20201216054612-986b41b23924 h1:QsnDpLLOKwHBBDa8nDws4DYNc/ryVW2vCpxCs09d4PY= 161 | golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 162 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 163 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 164 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 165 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 166 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 167 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 168 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 169 | golang.org/x/sys v0.0.0-20190226215855-775f8194d0f9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 170 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 171 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 172 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 173 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 174 | golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM= 175 | golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 176 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 177 | golang.org/x/sys v0.0.0-20201221093633-bc327ba9c2f0 h1:n+DPcgTwkgWzIFpLmoimYR2K2b0Ga5+Os4kayIN0vGo= 178 | golang.org/x/sys v0.0.0-20201221093633-bc327ba9c2f0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 179 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 180 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 181 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 182 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 183 | golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= 184 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 185 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 186 | golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 187 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 188 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 189 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 190 | golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 191 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 192 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 193 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 194 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 195 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 196 | golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 197 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa h1:5E4dL8+NgFOgjwbTKz+OOEGGhP+ectTmF842l6KjupQ= 198 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 199 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 200 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 201 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 202 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 203 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 204 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 205 | gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= 206 | gonum.org/v1/gonum v0.0.0-20190226202314-149afe6ec0b6/go.mod h1:jevfED4GnIEnJrWW55YmY9DMhajHcnkqVnEXmEtMyNI= 207 | gonum.org/v1/gonum v0.0.0-20190902003836-43865b531bee/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= 208 | gonum.org/v1/gonum v0.7.0 h1:Hdks0L0hgznZLG9nzXb8vZ0rRvqNvAcgAp84y7Mwkgw= 209 | gonum.org/v1/gonum v0.7.0/go.mod h1:L02bwd0sqlsvRv41G7wGWFCsVNZFv/k1xzGIxeANHGM= 210 | gonum.org/v1/gonum v0.8.1-0.20200930085651-eea0b5cb5cc9/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= 211 | gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM= 212 | gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= 213 | gonum.org/v1/netlib v0.0.0-20190221094214-0632e2ebbd2d/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= 214 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= 215 | gonum.org/v1/netlib v0.0.0-20200317120129-c5a04cffd98a h1:y158/g9tKwBGw9gnNENlUIi9NTJCoiQg2RFB1gr9atQ= 216 | gonum.org/v1/netlib v0.0.0-20200317120129-c5a04cffd98a/go.mod h1:6EVtvAMWMjOBOsTVX0xrjO4A6ULtEgWtAWHzqxDWdJs= 217 | gonum.org/v1/netlib v0.0.0-20201012070519-2390d26c3658 h1:/DNJ3wcvPHjTLVNG6rmSHK7uEwdBihyiJRJXB16wXoU= 218 | gonum.org/v1/netlib v0.0.0-20201012070519-2390d26c3658/go.mod h1:zQa7n16lh3Z6FbSTYgjG+KNhz1bA/b9t3plFEaGMp+A= 219 | gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= 220 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 221 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 222 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 223 | google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 224 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 225 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 226 | google.golang.org/genproto v0.0.0-20200911024640-645f7a48b24f h1:Yv4xsIx7HZOoyUGSJ2ksDyWE2qIBXROsZKt2ny3hCGM= 227 | google.golang.org/genproto v0.0.0-20200911024640-645f7a48b24f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 228 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d h1:HV9Z9qMhQEsdlvxNFELgQ11RkMzO3CMkjEySjCtuLes= 229 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 230 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 231 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 232 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 233 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 234 | google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= 235 | google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 236 | google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI= 237 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= 238 | google.golang.org/grpc/cmd/protoc-gen-go-grpc v0.0.0-20200910201057-6591123024b3/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= 239 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 240 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 241 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 242 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 243 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 244 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 245 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 246 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 247 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 248 | google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= 249 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 250 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 251 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 252 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 253 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 254 | gopkg.in/cheggaaa/pb.v1 v1.0.27 h1:kJdccidYzt3CaHD1crCFTS1hxyhSi059NhOFUf03YFo= 255 | gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= 256 | gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk= 257 | gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= 258 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 259 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 260 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 261 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= 262 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 263 | gorgonia.org/cu v0.9.0-beta/go.mod h1:RPEPIfaxxqUmeRe7T1T8a0NER+KxBI2McoLEXhP1Vd8= 264 | gorgonia.org/cu v0.9.3 h1:IkxE4NWXuZHqr8AnmgoB8WNQPZeD6u0EJNxYjDC0YgY= 265 | gorgonia.org/cu v0.9.3/go.mod h1:LgyAYDkN7HWhh8orGnCY2R8pP9PYbO44ivEbLMatkVU= 266 | gorgonia.org/dawson v1.1.0/go.mod h1:Px1mcziba8YUBIDsbzGwbKJ11uIblv/zkln4jNrZ9Ws= 267 | gorgonia.org/dawson v1.2.0 h1:hJ/aofhfkReSnJdSMDzypRZ/oWDL1TmeYOauBnXKdFw= 268 | gorgonia.org/dawson v1.2.0/go.mod h1:Px1mcziba8YUBIDsbzGwbKJ11uIblv/zkln4jNrZ9Ws= 269 | gorgonia.org/gorgonia v0.9.2/go.mod h1:ZtOb9f/wM2OMta1ISGspQ4roGDgz9d9dKOaPNvGR+ec= 270 | gorgonia.org/gorgonia v0.9.15 h1:2F+QN1B3Ijsr5fpQexf54iOTrmcNIcEPzwkc8ofHT1I= 271 | gorgonia.org/gorgonia v0.9.15/go.mod h1:/y+VJ/PPcf3NQ7teq0ugABCY4yStQ01/IMP8OKfbLsw= 272 | gorgonia.org/tensor v0.9.0-beta/go.mod h1:05Y4laKuVlj4qFoZIZW1q/9n1jZkgDBOLmKXZdBLG1w= 273 | gorgonia.org/tensor v0.9.11 h1:L7C+syNtsIcZ/91tJFT0QnAzXJyFt6tWSW6+URIucDM= 274 | gorgonia.org/tensor v0.9.11/go.mod h1:fsbuoeL1vV3fe8N+HZxEXJ7WI4z1pPP3luMBCgn0HAA= 275 | gorgonia.org/tensor v0.9.14 h1:EwM+jThO1lod2T+cswJoUbFAVAY8zMPdtqfflZbywEc= 276 | gorgonia.org/tensor v0.9.14/go.mod h1:fsbuoeL1vV3fe8N+HZxEXJ7WI4z1pPP3luMBCgn0HAA= 277 | gorgonia.org/vecf32 v0.7.0/go.mod h1:iHG+kvTMqGYA0SgahfO2k62WRnxmHsqAREGbayRDzy8= 278 | gorgonia.org/vecf32 v0.9.0 h1:PClazic1r+JVJ1dEzRXgeiVl4g1/Hf/w+wUSqnco1Xg= 279 | gorgonia.org/vecf32 v0.9.0/go.mod h1:NCc+5D2oxddRL11hd+pCB1PEyXWOyiQxfZ/1wwhOXCA= 280 | gorgonia.org/vecf64 v0.7.0/go.mod h1:1y4pmcSd+wh3phG+InwWQjYrqwyrtN9h27WLFVQfV1Q= 281 | gorgonia.org/vecf64 v0.9.0 h1:bgZDP5x0OzBF64PjMGC3EvTdOoMEcmfAh1VCUnZFm1A= 282 | gorgonia.org/vecf64 v0.9.0/go.mod h1:hp7IOWCnRiVQKON73kkC/AUMtEXyf9kGlVrtPQ9ccVA= 283 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 284 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 285 | modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= 286 | modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= 287 | modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= 288 | modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= 289 | modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= 290 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 291 | -------------------------------------------------------------------------------- /layer.go: -------------------------------------------------------------------------------- 1 | package yologo 2 | 3 | import ( 4 | "gorgonia.org/gorgonia" 5 | ) 6 | 7 | type layerN interface { 8 | String() string 9 | Type() string 10 | ToNode(g *gorgonia.ExprGraph, inputs ...*gorgonia.Node) (*gorgonia.Node, error) 11 | } 12 | -------------------------------------------------------------------------------- /max_pooling_layer.go: -------------------------------------------------------------------------------- 1 | package yologo 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pkg/errors" 7 | "gorgonia.org/gorgonia" 8 | "gorgonia.org/tensor" 9 | ) 10 | 11 | type maxPoolingLayer struct { 12 | size int 13 | stride int 14 | } 15 | 16 | func (l *maxPoolingLayer) String() string { 17 | return fmt.Sprintf("Maxpooling layer: Size->%[1]d Stride->%[2]d", l.size, l.stride) 18 | } 19 | 20 | func (l *maxPoolingLayer) Type() string { 21 | return "maxpool" 22 | } 23 | 24 | func (l *maxPoolingLayer) ToNode(g *gorgonia.ExprGraph, inputs ...*gorgonia.Node) (*gorgonia.Node, error) { 25 | shp := inputs[0].Shape() 26 | if shp[2]%2 == 0 { 27 | maxpoolOut, err := gorgonia.MaxPool2D(inputs[0], tensor.Shape{l.size, l.size}, []int{0, 0}, []int{l.stride, l.stride}) 28 | if err != nil { 29 | return &gorgonia.Node{}, errors.Wrap(err, "Can't prepare max pooling operation") 30 | } 31 | return maxpoolOut, nil 32 | } 33 | maxpoolOut, err := gorgonia.MaxPool2D(inputs[0], tensor.Shape{l.size, l.size}, []int{0, 1, 0, 1}, []int{l.stride, l.stride}) 34 | if err != nil { 35 | return &gorgonia.Node{}, errors.Wrap(err, "Can't prepare max pooling operation") 36 | } 37 | return maxpoolOut, nil 38 | } 39 | -------------------------------------------------------------------------------- /route_layer.go: -------------------------------------------------------------------------------- 1 | package yologo 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pkg/errors" 7 | "gorgonia.org/gorgonia" 8 | ) 9 | 10 | type routeLayer struct { 11 | firstLayerIdx int 12 | secondLayerIdx int 13 | } 14 | 15 | func (l *routeLayer) String() string { 16 | if l.secondLayerIdx != -1 { 17 | return fmt.Sprintf("Route layer: Start->%[1]d End->%[2]d", l.firstLayerIdx, l.secondLayerIdx) 18 | } 19 | return fmt.Sprintf("Route layer: Start->%[1]d", l.firstLayerIdx) 20 | } 21 | 22 | func (l *routeLayer) Type() string { 23 | return "route" 24 | } 25 | 26 | func (l *routeLayer) ToNode(g *gorgonia.ExprGraph, inputs ...*gorgonia.Node) (*gorgonia.Node, error) { 27 | concatNodes := []*gorgonia.Node{} 28 | concatNodes = append(concatNodes, inputs[l.firstLayerIdx]) 29 | if l.secondLayerIdx > 0 { 30 | concatNodes = append(concatNodes, inputs[l.secondLayerIdx]) 31 | } 32 | routeNode, err := gorgonia.Concat(1, concatNodes...) 33 | if err != nil { 34 | return &gorgonia.Node{}, errors.Wrap(err, "Can't prepare route operation") 35 | } 36 | 37 | return routeNode, nil 38 | } 39 | -------------------------------------------------------------------------------- /shortcut_layer.go: -------------------------------------------------------------------------------- 1 | package yologo 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pkg/errors" 7 | "gorgonia.org/gorgonia" 8 | ) 9 | 10 | type shortcutLayer struct { 11 | layerFromIdx int 12 | layerToIdx int 13 | } 14 | 15 | func (l *shortcutLayer) String() string { 16 | return fmt.Sprintf("Shortcut layer: index from->%[1]d | index to->%[2]d", l.layerFromIdx, l.layerToIdx) 17 | } 18 | 19 | func (l *shortcutLayer) Type() string { 20 | return "shortcut" 21 | } 22 | 23 | func (l *shortcutLayer) ToNode(g *gorgonia.ExprGraph, inputs ...*gorgonia.Node) (*gorgonia.Node, error) { 24 | if len(inputs) != 2 { 25 | return nil, fmt.Errorf("Shortcut layer can accept only two nodes, but got %d", len(inputs)) 26 | } 27 | 28 | addNode, err := gorgonia.Add(inputs[0], inputs[1]) 29 | if err != nil { 30 | return &gorgonia.Node{}, errors.Wrap(err, "Can't prepare shortcut operation") 31 | } 32 | 33 | return addNode, nil 34 | } 35 | -------------------------------------------------------------------------------- /test_network_data/dog_416x416.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LdDl/yolo-go/cf896359a9c497aca6c754322a06ffe74d32ffd4/test_network_data/dog_416x416.jpg -------------------------------------------------------------------------------- /test_network_data/download_weights_yolo_tiny_v3.sh: -------------------------------------------------------------------------------- 1 | curl https://pjreddie.com/media/files/yolov3-tiny.weights > yolov3-tiny.weights -------------------------------------------------------------------------------- /test_network_data/download_weights_yolo_v3.sh: -------------------------------------------------------------------------------- 1 | curl https://pjreddie.com/media/files/yolov3.weights > yolov3.weights -------------------------------------------------------------------------------- /test_network_data/yolov3-tiny.cfg: -------------------------------------------------------------------------------- 1 | [net] 2 | # Testing 3 | batch=1 4 | subdivisions=1 5 | # Training 6 | # batch=64 7 | # subdivisions=2 8 | width=416 9 | height=416 10 | channels=3 11 | momentum=0.9 12 | decay=0.0005 13 | angle=0 14 | saturation = 1.5 15 | exposure = 1.5 16 | hue=.1 17 | 18 | learning_rate=0.001 19 | burn_in=1000 20 | max_batches = 500200 21 | policy=steps 22 | steps=400000,450000 23 | scales=.1,.1 24 | 25 | [convolutional] 26 | batch_normalize=1 27 | filters=16 28 | size=3 29 | stride=1 30 | pad=1 31 | activation=leaky 32 | 33 | [maxpool] 34 | size=2 35 | stride=2 36 | 37 | [convolutional] 38 | batch_normalize=1 39 | filters=32 40 | size=3 41 | stride=1 42 | pad=1 43 | activation=leaky 44 | 45 | [maxpool] 46 | size=2 47 | stride=2 48 | 49 | [convolutional] 50 | batch_normalize=1 51 | filters=64 52 | size=3 53 | stride=1 54 | pad=1 55 | activation=leaky 56 | 57 | [maxpool] 58 | size=2 59 | stride=2 60 | 61 | [convolutional] 62 | batch_normalize=1 63 | filters=128 64 | size=3 65 | stride=1 66 | pad=1 67 | activation=leaky 68 | 69 | [maxpool] 70 | size=2 71 | stride=2 72 | 73 | [convolutional] 74 | batch_normalize=1 75 | filters=256 76 | size=3 77 | stride=1 78 | pad=1 79 | activation=leaky 80 | 81 | [maxpool] 82 | size=2 83 | stride=2 84 | 85 | [convolutional] 86 | batch_normalize=1 87 | filters=512 88 | size=3 89 | stride=1 90 | pad=1 91 | activation=leaky 92 | 93 | [maxpool] 94 | size=2 95 | stride=1 96 | 97 | [convolutional] 98 | batch_normalize=1 99 | filters=1024 100 | size=3 101 | stride=1 102 | pad=1 103 | activation=leaky 104 | 105 | ########### 106 | 107 | [convolutional] 108 | batch_normalize=1 109 | filters=256 110 | size=1 111 | stride=1 112 | pad=1 113 | activation=leaky 114 | 115 | [convolutional] 116 | filters=512 117 | size=3 118 | stride=1 119 | pad=1 120 | activation=leaky 121 | batch_normalize=1 122 | 123 | [convolutional] 124 | size=1 125 | stride=1 126 | pad=1 127 | filters=255 128 | activation=linear 129 | 130 | 131 | 132 | [yolo] 133 | mask = 3,4,5 134 | anchors = 10,14, 23,27, 37,58, 81,82, 135,169, 344,319 135 | classes=80 136 | num=6 137 | jitter=.3 138 | ignore_thresh = .7 139 | truth_thresh = 1 140 | random=1 141 | 142 | [route] 143 | layers = -4 144 | 145 | [convolutional] 146 | batch_normalize=1 147 | filters=128 148 | size=1 149 | stride=1 150 | pad=1 151 | activation=leaky 152 | 153 | [upsample] 154 | stride=2 155 | 156 | [route] 157 | layers = -1, 8 158 | 159 | [convolutional] 160 | batch_normalize=1 161 | filters=256 162 | size=3 163 | stride=1 164 | pad=1 165 | activation=leaky 166 | 167 | [convolutional] 168 | size=1 169 | stride=1 170 | pad=1 171 | filters=255 172 | activation=linear 173 | 174 | [yolo] 175 | mask = 0,1,2 176 | anchors = 10,14, 23,27, 37,58, 81,82, 135,169, 344,319 177 | classes=80 178 | num=6 179 | jitter=.3 180 | ignore_thresh = .7 181 | truth_thresh = 1 182 | random=1 183 | -------------------------------------------------------------------------------- /test_network_data/yolov3.cfg: -------------------------------------------------------------------------------- 1 | [net] 2 | # Testing 3 | batch=1 4 | subdivisions=1 5 | # Training 6 | # batch=64 7 | # subdivisions=16 8 | width=416 9 | height=416 10 | channels=3 11 | momentum=0.9 12 | decay=0.0005 13 | angle=0 14 | saturation = 1.5 15 | exposure = 1.5 16 | hue=.1 17 | 18 | learning_rate=0.001 19 | burn_in=1000 20 | max_batches = 500200 21 | policy=steps 22 | steps=400000,450000 23 | scales=.1,.1 24 | 25 | [convolutional] 26 | batch_normalize=1 27 | filters=32 28 | size=3 29 | stride=1 30 | pad=1 31 | activation=leaky 32 | 33 | # Downsample 34 | 35 | [convolutional] 36 | batch_normalize=1 37 | filters=64 38 | size=3 39 | stride=2 40 | pad=1 41 | activation=leaky 42 | 43 | [convolutional] 44 | batch_normalize=1 45 | filters=32 46 | size=1 47 | stride=1 48 | pad=1 49 | activation=leaky 50 | 51 | [convolutional] 52 | batch_normalize=1 53 | filters=64 54 | size=3 55 | stride=1 56 | pad=1 57 | activation=leaky 58 | 59 | [shortcut] 60 | from=-3 61 | activation=linear 62 | 63 | # Downsample 64 | 65 | [convolutional] 66 | batch_normalize=1 67 | filters=128 68 | size=3 69 | stride=2 70 | pad=1 71 | activation=leaky 72 | 73 | [convolutional] 74 | batch_normalize=1 75 | filters=64 76 | size=1 77 | stride=1 78 | pad=1 79 | activation=leaky 80 | 81 | [convolutional] 82 | batch_normalize=1 83 | filters=128 84 | size=3 85 | stride=1 86 | pad=1 87 | activation=leaky 88 | 89 | [shortcut] 90 | from=-3 91 | activation=linear 92 | 93 | [convolutional] 94 | batch_normalize=1 95 | filters=64 96 | size=1 97 | stride=1 98 | pad=1 99 | activation=leaky 100 | 101 | [convolutional] 102 | batch_normalize=1 103 | filters=128 104 | size=3 105 | stride=1 106 | pad=1 107 | activation=leaky 108 | 109 | [shortcut] 110 | from=-3 111 | activation=linear 112 | 113 | # Downsample 114 | 115 | [convolutional] 116 | batch_normalize=1 117 | filters=256 118 | size=3 119 | stride=2 120 | pad=1 121 | activation=leaky 122 | 123 | [convolutional] 124 | batch_normalize=1 125 | filters=128 126 | size=1 127 | stride=1 128 | pad=1 129 | activation=leaky 130 | 131 | [convolutional] 132 | batch_normalize=1 133 | filters=256 134 | size=3 135 | stride=1 136 | pad=1 137 | activation=leaky 138 | 139 | [shortcut] 140 | from=-3 141 | activation=linear 142 | 143 | [convolutional] 144 | batch_normalize=1 145 | filters=128 146 | size=1 147 | stride=1 148 | pad=1 149 | activation=leaky 150 | 151 | [convolutional] 152 | batch_normalize=1 153 | filters=256 154 | size=3 155 | stride=1 156 | pad=1 157 | activation=leaky 158 | 159 | [shortcut] 160 | from=-3 161 | activation=linear 162 | 163 | [convolutional] 164 | batch_normalize=1 165 | filters=128 166 | size=1 167 | stride=1 168 | pad=1 169 | activation=leaky 170 | 171 | [convolutional] 172 | batch_normalize=1 173 | filters=256 174 | size=3 175 | stride=1 176 | pad=1 177 | activation=leaky 178 | 179 | [shortcut] 180 | from=-3 181 | activation=linear 182 | 183 | [convolutional] 184 | batch_normalize=1 185 | filters=128 186 | size=1 187 | stride=1 188 | pad=1 189 | activation=leaky 190 | 191 | [convolutional] 192 | batch_normalize=1 193 | filters=256 194 | size=3 195 | stride=1 196 | pad=1 197 | activation=leaky 198 | 199 | [shortcut] 200 | from=-3 201 | activation=linear 202 | 203 | 204 | [convolutional] 205 | batch_normalize=1 206 | filters=128 207 | size=1 208 | stride=1 209 | pad=1 210 | activation=leaky 211 | 212 | [convolutional] 213 | batch_normalize=1 214 | filters=256 215 | size=3 216 | stride=1 217 | pad=1 218 | activation=leaky 219 | 220 | [shortcut] 221 | from=-3 222 | activation=linear 223 | 224 | [convolutional] 225 | batch_normalize=1 226 | filters=128 227 | size=1 228 | stride=1 229 | pad=1 230 | activation=leaky 231 | 232 | [convolutional] 233 | batch_normalize=1 234 | filters=256 235 | size=3 236 | stride=1 237 | pad=1 238 | activation=leaky 239 | 240 | [shortcut] 241 | from=-3 242 | activation=linear 243 | 244 | [convolutional] 245 | batch_normalize=1 246 | filters=128 247 | size=1 248 | stride=1 249 | pad=1 250 | activation=leaky 251 | 252 | [convolutional] 253 | batch_normalize=1 254 | filters=256 255 | size=3 256 | stride=1 257 | pad=1 258 | activation=leaky 259 | 260 | [shortcut] 261 | from=-3 262 | activation=linear 263 | 264 | [convolutional] 265 | batch_normalize=1 266 | filters=128 267 | size=1 268 | stride=1 269 | pad=1 270 | activation=leaky 271 | 272 | [convolutional] 273 | batch_normalize=1 274 | filters=256 275 | size=3 276 | stride=1 277 | pad=1 278 | activation=leaky 279 | 280 | [shortcut] 281 | from=-3 282 | activation=linear 283 | 284 | # Downsample 285 | 286 | [convolutional] 287 | batch_normalize=1 288 | filters=512 289 | size=3 290 | stride=2 291 | pad=1 292 | activation=leaky 293 | 294 | [convolutional] 295 | batch_normalize=1 296 | filters=256 297 | size=1 298 | stride=1 299 | pad=1 300 | activation=leaky 301 | 302 | [convolutional] 303 | batch_normalize=1 304 | filters=512 305 | size=3 306 | stride=1 307 | pad=1 308 | activation=leaky 309 | 310 | [shortcut] 311 | from=-3 312 | activation=linear 313 | 314 | 315 | [convolutional] 316 | batch_normalize=1 317 | filters=256 318 | size=1 319 | stride=1 320 | pad=1 321 | activation=leaky 322 | 323 | [convolutional] 324 | batch_normalize=1 325 | filters=512 326 | size=3 327 | stride=1 328 | pad=1 329 | activation=leaky 330 | 331 | [shortcut] 332 | from=-3 333 | activation=linear 334 | 335 | 336 | [convolutional] 337 | batch_normalize=1 338 | filters=256 339 | size=1 340 | stride=1 341 | pad=1 342 | activation=leaky 343 | 344 | [convolutional] 345 | batch_normalize=1 346 | filters=512 347 | size=3 348 | stride=1 349 | pad=1 350 | activation=leaky 351 | 352 | [shortcut] 353 | from=-3 354 | activation=linear 355 | 356 | 357 | [convolutional] 358 | batch_normalize=1 359 | filters=256 360 | size=1 361 | stride=1 362 | pad=1 363 | activation=leaky 364 | 365 | [convolutional] 366 | batch_normalize=1 367 | filters=512 368 | size=3 369 | stride=1 370 | pad=1 371 | activation=leaky 372 | 373 | [shortcut] 374 | from=-3 375 | activation=linear 376 | 377 | [convolutional] 378 | batch_normalize=1 379 | filters=256 380 | size=1 381 | stride=1 382 | pad=1 383 | activation=leaky 384 | 385 | [convolutional] 386 | batch_normalize=1 387 | filters=512 388 | size=3 389 | stride=1 390 | pad=1 391 | activation=leaky 392 | 393 | [shortcut] 394 | from=-3 395 | activation=linear 396 | 397 | 398 | [convolutional] 399 | batch_normalize=1 400 | filters=256 401 | size=1 402 | stride=1 403 | pad=1 404 | activation=leaky 405 | 406 | [convolutional] 407 | batch_normalize=1 408 | filters=512 409 | size=3 410 | stride=1 411 | pad=1 412 | activation=leaky 413 | 414 | [shortcut] 415 | from=-3 416 | activation=linear 417 | 418 | 419 | [convolutional] 420 | batch_normalize=1 421 | filters=256 422 | size=1 423 | stride=1 424 | pad=1 425 | activation=leaky 426 | 427 | [convolutional] 428 | batch_normalize=1 429 | filters=512 430 | size=3 431 | stride=1 432 | pad=1 433 | activation=leaky 434 | 435 | [shortcut] 436 | from=-3 437 | activation=linear 438 | 439 | [convolutional] 440 | batch_normalize=1 441 | filters=256 442 | size=1 443 | stride=1 444 | pad=1 445 | activation=leaky 446 | 447 | [convolutional] 448 | batch_normalize=1 449 | filters=512 450 | size=3 451 | stride=1 452 | pad=1 453 | activation=leaky 454 | 455 | [shortcut] 456 | from=-3 457 | activation=linear 458 | 459 | # Downsample 460 | 461 | [convolutional] 462 | batch_normalize=1 463 | filters=1024 464 | size=3 465 | stride=2 466 | pad=1 467 | activation=leaky 468 | 469 | [convolutional] 470 | batch_normalize=1 471 | filters=512 472 | size=1 473 | stride=1 474 | pad=1 475 | activation=leaky 476 | 477 | [convolutional] 478 | batch_normalize=1 479 | filters=1024 480 | size=3 481 | stride=1 482 | pad=1 483 | activation=leaky 484 | 485 | [shortcut] 486 | from=-3 487 | activation=linear 488 | 489 | [convolutional] 490 | batch_normalize=1 491 | filters=512 492 | size=1 493 | stride=1 494 | pad=1 495 | activation=leaky 496 | 497 | [convolutional] 498 | batch_normalize=1 499 | filters=1024 500 | size=3 501 | stride=1 502 | pad=1 503 | activation=leaky 504 | 505 | [shortcut] 506 | from=-3 507 | activation=linear 508 | 509 | [convolutional] 510 | batch_normalize=1 511 | filters=512 512 | size=1 513 | stride=1 514 | pad=1 515 | activation=leaky 516 | 517 | [convolutional] 518 | batch_normalize=1 519 | filters=1024 520 | size=3 521 | stride=1 522 | pad=1 523 | activation=leaky 524 | 525 | [shortcut] 526 | from=-3 527 | activation=linear 528 | 529 | [convolutional] 530 | batch_normalize=1 531 | filters=512 532 | size=1 533 | stride=1 534 | pad=1 535 | activation=leaky 536 | 537 | [convolutional] 538 | batch_normalize=1 539 | filters=1024 540 | size=3 541 | stride=1 542 | pad=1 543 | activation=leaky 544 | 545 | [shortcut] 546 | from=-3 547 | activation=linear 548 | 549 | ###################### 550 | 551 | [convolutional] 552 | batch_normalize=1 553 | filters=512 554 | size=1 555 | stride=1 556 | pad=1 557 | activation=leaky 558 | 559 | [convolutional] 560 | batch_normalize=1 561 | size=3 562 | stride=1 563 | pad=1 564 | filters=1024 565 | activation=leaky 566 | 567 | [convolutional] 568 | batch_normalize=1 569 | filters=512 570 | size=1 571 | stride=1 572 | pad=1 573 | activation=leaky 574 | 575 | [convolutional] 576 | batch_normalize=1 577 | size=3 578 | stride=1 579 | pad=1 580 | filters=1024 581 | activation=leaky 582 | 583 | [convolutional] 584 | batch_normalize=1 585 | filters=512 586 | size=1 587 | stride=1 588 | pad=1 589 | activation=leaky 590 | 591 | [convolutional] 592 | batch_normalize=1 593 | size=3 594 | stride=1 595 | pad=1 596 | filters=1024 597 | activation=leaky 598 | 599 | [convolutional] 600 | size=1 601 | stride=1 602 | pad=1 603 | filters=255 604 | activation=linear 605 | 606 | 607 | [yolo] 608 | mask = 6,7,8 609 | anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326 610 | classes=80 611 | num=9 612 | jitter=.3 613 | ignore_thresh = .7 614 | truth_thresh = 1 615 | random=1 616 | 617 | 618 | [route] 619 | layers = -4 620 | 621 | [convolutional] 622 | batch_normalize=1 623 | filters=256 624 | size=1 625 | stride=1 626 | pad=1 627 | activation=leaky 628 | 629 | [upsample] 630 | stride=2 631 | 632 | [route] 633 | layers = -1, 61 634 | 635 | 636 | 637 | [convolutional] 638 | batch_normalize=1 639 | filters=256 640 | size=1 641 | stride=1 642 | pad=1 643 | activation=leaky 644 | 645 | [convolutional] 646 | batch_normalize=1 647 | size=3 648 | stride=1 649 | pad=1 650 | filters=512 651 | activation=leaky 652 | 653 | [convolutional] 654 | batch_normalize=1 655 | filters=256 656 | size=1 657 | stride=1 658 | pad=1 659 | activation=leaky 660 | 661 | [convolutional] 662 | batch_normalize=1 663 | size=3 664 | stride=1 665 | pad=1 666 | filters=512 667 | activation=leaky 668 | 669 | [convolutional] 670 | batch_normalize=1 671 | filters=256 672 | size=1 673 | stride=1 674 | pad=1 675 | activation=leaky 676 | 677 | [convolutional] 678 | batch_normalize=1 679 | size=3 680 | stride=1 681 | pad=1 682 | filters=512 683 | activation=leaky 684 | 685 | [convolutional] 686 | size=1 687 | stride=1 688 | pad=1 689 | filters=255 690 | activation=linear 691 | 692 | 693 | [yolo] 694 | mask = 3,4,5 695 | anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326 696 | classes=80 697 | num=9 698 | jitter=.3 699 | ignore_thresh = .7 700 | truth_thresh = 1 701 | random=1 702 | 703 | 704 | 705 | [route] 706 | layers = -4 707 | 708 | [convolutional] 709 | batch_normalize=1 710 | filters=128 711 | size=1 712 | stride=1 713 | pad=1 714 | activation=leaky 715 | 716 | [upsample] 717 | stride=2 718 | 719 | [route] 720 | layers = -1, 36 721 | 722 | 723 | 724 | [convolutional] 725 | batch_normalize=1 726 | filters=128 727 | size=1 728 | stride=1 729 | pad=1 730 | activation=leaky 731 | 732 | [convolutional] 733 | batch_normalize=1 734 | size=3 735 | stride=1 736 | pad=1 737 | filters=256 738 | activation=leaky 739 | 740 | [convolutional] 741 | batch_normalize=1 742 | filters=128 743 | size=1 744 | stride=1 745 | pad=1 746 | activation=leaky 747 | 748 | [convolutional] 749 | batch_normalize=1 750 | size=3 751 | stride=1 752 | pad=1 753 | filters=256 754 | activation=leaky 755 | 756 | [convolutional] 757 | batch_normalize=1 758 | filters=128 759 | size=1 760 | stride=1 761 | pad=1 762 | activation=leaky 763 | 764 | [convolutional] 765 | batch_normalize=1 766 | size=3 767 | stride=1 768 | pad=1 769 | filters=256 770 | activation=leaky 771 | 772 | [convolutional] 773 | size=1 774 | stride=1 775 | pad=1 776 | filters=255 777 | activation=linear 778 | 779 | 780 | [yolo] 781 | mask = 0,1,2 782 | anchors = 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326 783 | classes=80 784 | num=9 785 | jitter=.3 786 | ignore_thresh = .7 787 | truth_thresh = 1 788 | random=1 789 | -------------------------------------------------------------------------------- /test_yolo_op_data/1input.[(10, 13), (16, 30), (33, 23)].npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LdDl/yolo-go/cf896359a9c497aca6c754322a06ffe74d32ffd4/test_yolo_op_data/1input.[(10, 13), (16, 30), (33, 23)].npy -------------------------------------------------------------------------------- /test_yolo_op_data/1input.[(116, 90), (156, 198), (373, 326)].npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LdDl/yolo-go/cf896359a9c497aca6c754322a06ffe74d32ffd4/test_yolo_op_data/1input.[(116, 90), (156, 198), (373, 326)].npy -------------------------------------------------------------------------------- /test_yolo_op_data/1input.[(30, 61), (62, 45), (59, 119)].npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LdDl/yolo-go/cf896359a9c497aca6c754322a06ffe74d32ffd4/test_yolo_op_data/1input.[(30, 61), (62, 45), (59, 119)].npy -------------------------------------------------------------------------------- /test_yolo_op_data/1output.[(10, 13), (16, 30), (33, 23)].npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LdDl/yolo-go/cf896359a9c497aca6c754322a06ffe74d32ffd4/test_yolo_op_data/1output.[(10, 13), (16, 30), (33, 23)].npy -------------------------------------------------------------------------------- /test_yolo_op_data/1output.[(116, 90), (156, 198), (373, 326)].npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LdDl/yolo-go/cf896359a9c497aca6c754322a06ffe74d32ffd4/test_yolo_op_data/1output.[(116, 90), (156, 198), (373, 326)].npy -------------------------------------------------------------------------------- /test_yolo_op_data/1output.[(30, 61), (62, 45), (59, 119)].npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LdDl/yolo-go/cf896359a9c497aca6c754322a06ffe74d32ffd4/test_yolo_op_data/1output.[(30, 61), (62, 45), (59, 119)].npy -------------------------------------------------------------------------------- /test_yolo_op_data/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LdDl/yolo-go/cf896359a9c497aca6c754322a06ffe74d32ffd4/test_yolo_op_data/test.jpg -------------------------------------------------------------------------------- /test_yolo_op_data/test.txt: -------------------------------------------------------------------------------- 1 | 7 0.7475961538461539 0.2127403846153846 0.2932692307692308 0.18028846153846154 2 | 1 0.45072115384615385 0.484375 0.5841346153846154 0.49759615384615385 3 | 16 0.28846153846153844 0.6646634615384616 0.24519230769230768 0.5649038461538461 -------------------------------------------------------------------------------- /upsample_layer.go: -------------------------------------------------------------------------------- 1 | package yologo 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pkg/errors" 7 | "gorgonia.org/gorgonia" 8 | ) 9 | 10 | type upsampleLayer struct { 11 | scale int 12 | } 13 | 14 | func (l *upsampleLayer) String() string { 15 | return fmt.Sprintf("Upsample layer: Scale->%[1]d", l.scale) 16 | } 17 | 18 | func (l *upsampleLayer) Type() string { 19 | return "upsample" 20 | } 21 | 22 | func (l *upsampleLayer) ToNode(g *gorgonia.ExprGraph, inputs ...*gorgonia.Node) (*gorgonia.Node, error) { 23 | upsampleOut, err := Upsample2D(inputs[0], l.scale) 24 | if err != nil { 25 | return &gorgonia.Node{}, errors.Wrap(err, "Can't prepare upsample operation") 26 | } 27 | return upsampleOut, nil 28 | } 29 | -------------------------------------------------------------------------------- /upsample_op.go: -------------------------------------------------------------------------------- 1 | package yologo 2 | 3 | import ( 4 | "fmt" 5 | "hash" 6 | "hash/fnv" 7 | 8 | "github.com/chewxy/hm" 9 | "github.com/pkg/errors" 10 | "gorgonia.org/gorgonia" 11 | "gorgonia.org/tensor" 12 | ) 13 | 14 | type upsampleOp struct { 15 | stride int 16 | } 17 | 18 | func newUpsampleOp(inputShape tensor.Shape, stride int) *upsampleOp { 19 | upsampleop := &upsampleOp{ 20 | stride: stride, 21 | } 22 | return upsampleop 23 | } 24 | 25 | //Upsample2D - simply upscaling Tensor by scale factor. 26 | /* 27 | 1, 2 28 | 3, 4 29 | converts to 30 | 1,1,2,2 31 | 1,1,2,2 32 | 3,3,4,4, 33 | 3,3,4,4, 34 | */ 35 | func Upsample2D(x *gorgonia.Node, scale int) (*gorgonia.Node, error) { 36 | if scale < 1 { 37 | return nil, errors.Errorf("Upsample scale %v does not make sense", scale) 38 | } 39 | xShape := x.Shape() 40 | op := newUpsampleOp(xShape, scale-1) 41 | retVal, err := gorgonia.ApplyOp(op, x) 42 | return retVal, err 43 | } 44 | 45 | func (op *upsampleOp) Arity() int { 46 | 47 | return 1 48 | } 49 | func (op *upsampleOp) ReturnsPtr() bool { return false } 50 | 51 | func (op *upsampleOp) CallsExtern() bool { return false } 52 | 53 | func (op *upsampleOp) WriteHash(h hash.Hash) { 54 | fmt.Fprintf(h, "Upsample{}(stride: (%d))", op.stride) 55 | } 56 | func (op *upsampleOp) Hashcode() uint32 { 57 | h := fnv.New32a() 58 | op.WriteHash(h) 59 | return h.Sum32() 60 | } 61 | 62 | func (op *upsampleOp) String() string { 63 | return fmt.Sprintf("Upsample{}(stride: (%d))", op.stride) 64 | } 65 | func (op *upsampleOp) InferShape(inputs ...gorgonia.DimSizer) (tensor.Shape, error) { 66 | s := inputs[0].(tensor.Shape).Clone() 67 | s[2] = s[2] * (1 + op.stride) 68 | s[3] = s[3] * (1 + op.stride) 69 | return s, nil 70 | } 71 | func (op *upsampleOp) Type() hm.Type { 72 | a := hm.TypeVariable('a') 73 | t := gorgonia.TensorType{Dims: 4, Of: a} 74 | return hm.NewFnType(t, t) 75 | } 76 | func (op *upsampleOp) OverwritesInput() int { return -1 } 77 | 78 | func (op *upsampleOp) checkInput(inputs ...gorgonia.Value) (tensor.Tensor, error) { 79 | if err := checkArity(op, len(inputs)); err != nil { 80 | return nil, err 81 | } 82 | var in tensor.Tensor 83 | var ok bool 84 | if in, ok = inputs[0].(tensor.Tensor); !ok { 85 | return nil, errors.Errorf("Expected input to be a tensor") 86 | } 87 | 88 | if in.Shape().Dims() != 4 { 89 | return nil, errors.Errorf("Expected input to have 4 dimensions") 90 | } 91 | return in, nil 92 | } 93 | 94 | func (op *upsampleOp) Do(inputs ...gorgonia.Value) (retVal gorgonia.Value, err error) { 95 | var in tensor.Tensor 96 | if in, err = op.checkInput(inputs...); err != nil { 97 | return nil, err 98 | } 99 | inShp := in.Shape() 100 | b, c, h, w := inShp[0], inShp[1], inShp[2], inShp[3] 101 | 102 | out := tensor.New(tensor.Of(in.Dtype()), tensor.WithShape(b, c, h*(1+op.stride), w*(1+op.stride)), tensor.WithEngine(in.Engine())) 103 | for bi := 0; bi < b; bi++ { 104 | for ci := 0; ci < c; ci++ { 105 | for hi := 0; hi < h; hi++ { 106 | for wi := 0; wi < w; wi++ { 107 | val, err := in.At(bi, ci, hi, wi) 108 | if err != nil { 109 | return nil, errors.Errorf("Error accessing input data at [%v, %v, %v, %v]", bi, ci, hi, wi) 110 | } 111 | hout := hi * (op.stride + 1) 112 | wout := wi * (op.stride + 1) 113 | for shi := 0; shi <= op.stride; shi++ { 114 | for swi := 0; swi <= op.stride; swi++ { 115 | out.SetAt(val, bi, ci, hout+shi, wout+swi) 116 | } 117 | } 118 | } 119 | } 120 | } 121 | } 122 | 123 | return out, nil 124 | } 125 | 126 | func (op *upsampleOp) DiffWRT(inputs int) []bool { return []bool{true} } 127 | 128 | func (op *upsampleOp) SymDiff(inputs gorgonia.Nodes, output, grad *gorgonia.Node) (retVal gorgonia.Nodes, err error) { 129 | if err = checkArity(op, len(inputs)); err != nil { 130 | return 131 | } 132 | input := inputs[0] 133 | 134 | var op2 upsampleOp 135 | op2 = *op 136 | diff := &upsampleDiffOp{op2} 137 | 138 | var ret *gorgonia.Node 139 | if ret, err = gorgonia.ApplyOp(diff, input, output, grad); err != nil { 140 | return nil, err 141 | } 142 | return gorgonia.Nodes{ret}, nil 143 | } 144 | 145 | type upsampleDiffOp struct { 146 | upsampleOp 147 | } 148 | 149 | func (op *upsampleDiffOp) Arity() int { return 3 } 150 | 151 | func (op *upsampleDiffOp) Type() hm.Type { 152 | a := hm.TypeVariable('a') 153 | t := gorgonia.TensorType{Dims: 4, Of: a} 154 | return hm.NewFnType(t, t, t, t) 155 | } 156 | 157 | func (op *upsampleDiffOp) InferShape(inputs ...gorgonia.DimSizer) (tensor.Shape, error) { 158 | return inputs[0].(tensor.Shape).Clone(), nil 159 | } 160 | 161 | func (op *upsampleDiffOp) checkInput(inputs ...gorgonia.Value) (in, pooled, pooledGrad tensor.Tensor, err error) { 162 | if err = checkArity(op, len(inputs)); err != nil { 163 | return 164 | } 165 | 166 | var ok bool 167 | if in, ok = inputs[0].(tensor.Tensor); !ok { 168 | err = errors.Errorf("Expected input to be a tensor") 169 | return 170 | } 171 | if in.Shape().Dims() != 4 { 172 | err = errors.Errorf("Expected input to have 4 dimensions") 173 | return 174 | } 175 | 176 | if pooled, ok = inputs[1].(tensor.Tensor); !ok { 177 | err = errors.Errorf("Expected pooled to be a tensor") 178 | return 179 | } 180 | 181 | if pooledGrad, ok = inputs[2].(tensor.Tensor); !ok { 182 | err = errors.Errorf("Expected pooledGrad to be a tensor") 183 | return 184 | } 185 | return 186 | } 187 | 188 | func (op *upsampleDiffOp) Do(inputs ...gorgonia.Value) (retVal gorgonia.Value, err error) { 189 | var gradIn tensor.Tensor 190 | in, pooled, pooledGrad, err := op.checkInput(inputs...) 191 | if err != nil { 192 | return nil, err 193 | } 194 | insh := in.Shape() 195 | gradIn = tensor.New(tensor.Of(in.Dtype()), tensor.WithShape(in.Shape().Clone()...), tensor.WithEngine(in.Engine())) 196 | b, c, h, w := insh[0], insh[1], insh[2], insh[3] 197 | for bi := 0; bi < b; bi++ { 198 | for ci := 0; ci < c; ci++ { 199 | for hi := 0; hi < h; hi++ { 200 | for wi := 0; wi < w; wi++ { 201 | summ := 0. 202 | for sh := 0; sh <= op.stride; sh++ { 203 | for sw := 0; sw <= op.stride; sw++ { 204 | val, err := pooledGrad.At(bi, ci, hi*(op.stride+1)+sh, wi*(op.stride+1)+sw) //! 205 | if err != nil { 206 | return nil, errors.Errorf("Error accessing input data at [%v, %v, %v, %v]", bi, ci, hi, wi) 207 | } 208 | if pooled.Dtype() == tensor.Float32 { 209 | summ += float64(val.(float32)) 210 | } else if pooled.Dtype() == tensor.Float64 { 211 | summ += val.(float64) 212 | } 213 | } 214 | } 215 | if pooled.Dtype() == tensor.Float32 { 216 | gradIn.SetAt(float32(summ), bi, ci, hi, wi) 217 | } 218 | if pooled.Dtype() == tensor.Float64 { 219 | gradIn.SetAt(summ, bi, ci, hi, wi) 220 | } 221 | } 222 | } 223 | } 224 | } 225 | return gradIn, nil 226 | } 227 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package yologo 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "image" 7 | "image/color" 8 | "image/draw" 9 | "image/jpeg" 10 | "math" 11 | "os" 12 | 13 | "github.com/chewxy/math32" 14 | "gorgonia.org/gorgonia" 15 | "gorgonia.org/tensor" 16 | ) 17 | 18 | // Slice Just alias 19 | var Slice = gorgonia.S 20 | 21 | func rectifyBoxF32(x, y, h, w float32, imgSize int) image.Rectangle { 22 | return image.Rect(MaxInt(int(x-w/2), 0), MaxInt(int(y-h/2), 0), MinInt(int(x+w/2+1), imgSize), MinInt(int(y+h/2+1), imgSize)) 23 | } 24 | 25 | func findIntElement(arr []int, ele int) int { 26 | for i := range arr { 27 | if arr[i] == ele { 28 | return i 29 | } 30 | } 31 | return -1 32 | } 33 | 34 | func tensorToF32(in tensor.Tensor) (input32 []float32, err error) { 35 | in.Reshape(in.Shape().TotalSize()) 36 | input32 = make([]float32, 0) 37 | input32 = make([]float32, in.Shape().TotalSize()) 38 | for i := 0; i < in.Shape()[0]; i++ { 39 | var buf interface{} 40 | buf, err = in.At(i) 41 | switch in.Dtype() { 42 | case tensor.Float32: 43 | input32[i] = buf.(float32) 44 | break 45 | case tensor.Float64: 46 | input32[i] = float32(buf.(float64)) 47 | break 48 | default: 49 | return nil, fmt.Errorf("convertTensorToFloat32() supports only Float32/Float64 types of tensor") 50 | } 51 | } 52 | return input32, nil 53 | } 54 | 55 | // IOUFloat32 Intersection Over Union for float32 56 | func IOUFloat32(r1, r2 image.Rectangle) float32 { 57 | intersection := r1.Intersect(r2) 58 | interArea := intersection.Dx() * intersection.Dy() 59 | r1Area := r1.Dx() * r1.Dy() 60 | r2Area := r2.Dx() * r2.Dy() 61 | return float32(interArea) / float32(r1Area+r2Area-interArea) 62 | } 63 | 64 | func getBestIOUF32(input, target []float32, numClasses, dims int) [][]float32 { 65 | ious := make([][]float32, 0) 66 | imgsize := float32(dims) 67 | for i := 0; i < len(input); i = i + numClasses + 5 { 68 | ious = append(ious, []float32{0, -1}) 69 | r1 := rectifyBoxF32(input[i], input[i+1], input[i+2], input[i+3], dims) 70 | for j := 0; j < len(target); j = j + 5 { 71 | r2 := rectifyBoxF32(target[j+1]*imgsize, target[j+2]*imgsize, target[j+3]*imgsize, target[j+4]*imgsize, dims) 72 | curiou := IOUFloat32(r1, r2) 73 | if curiou > ious[i/(5+numClasses)][0] { 74 | ious[i/(5+numClasses)][0] = curiou 75 | ious[i/(5+numClasses)][1] = float32(j / 5) 76 | } 77 | } 78 | } 79 | return ious 80 | } 81 | 82 | // Rectify Creates rectangle 83 | func Rectify(x, y, h, w, maxwidth, maxheight int) image.Rectangle { 84 | return image.Rect(MaxInt(x-w/2, 0), MaxInt(y-h/2, 0), MinInt(x+w/2+1, maxwidth), MinInt(y+h/2+1, maxheight)) 85 | } 86 | 87 | // MaxInt Maximum between two integers 88 | func MaxInt(a, b int) int { 89 | if a > b { 90 | return a 91 | } 92 | return b 93 | } 94 | 95 | // MinInt Minimum between two integers 96 | func MinInt(a, b int) int { 97 | if a < b { 98 | return a 99 | } 100 | return b 101 | } 102 | 103 | // arityer Duck typing for arity check 104 | type arityer interface { 105 | Arity() int 106 | } 107 | 108 | func checkArity(op arityer, inputs int) error { 109 | if inputs != op.Arity() && op.Arity() >= 0 { 110 | return fmt.Errorf("%v has an arity of %d. Got %d instead", op, op.Arity(), inputs) 111 | } 112 | return nil 113 | } 114 | 115 | func _sigmoidf32(x float32) float32 { 116 | if x < -88 { 117 | return 0 118 | } 119 | if x > 15 { 120 | return 1 121 | } 122 | return 1.0 / (1.0 + math32.Exp(-x)) 123 | } 124 | 125 | // Float32frombytes Converts []byte to float32 126 | func Float32frombytes(bytes []byte) float32 { 127 | bits := binary.LittleEndian.Uint32(bytes) 128 | float := math.Float32frombits(bits) 129 | return float 130 | } 131 | 132 | // GetFloat32Image Returns []float32 representation of image file 133 | func GetFloat32Image(fname string, resizeWidth, resizeHeight int) ([]float32, error) { 134 | file, err := os.Open(fname) 135 | if err != nil { 136 | return nil, err 137 | } 138 | defer file.Close() 139 | img, err := jpeg.Decode(file) 140 | if err != nil { 141 | return nil, err 142 | } 143 | imgResized := resizeImage(img, resizeWidth, resizeHeight) 144 | return Image2Float32(imgResized) 145 | } 146 | 147 | // Image2Float32 Returns []float32 representation of image.Image 148 | func Image2Float32(img image.Image) ([]float32, error) { 149 | channelsNum := 3 // Static for RGB 150 | width := img.Bounds().Dx() 151 | height := img.Bounds().Dy() 152 | imgwh := width * height 153 | imgSize := imgwh * channelsNum 154 | ans := make([]float32, imgSize) 155 | for x := 0; x < width; x++ { 156 | for y := 0; y < height; y++ { 157 | r, g, b, _ := img.At(y, x).RGBA() 158 | rpix, gpix, bpix := float32(r>>8)/float32(255.0), float32(g>>8)/float32(255.0), float32(b>>8)/float32(255.0) 159 | ans[y+x*height] = rpix 160 | ans[y+x*height+imgwh] = gpix 161 | ans[y+x*height+imgwh+imgwh] = bpix 162 | } 163 | } 164 | return ans, nil 165 | } 166 | 167 | // Naive image resizing. See ref. https://stackoverflow.com/a/56411381 168 | func resizeImage(img image.Image, width int, height int) image.Image { 169 | minX := img.Bounds().Min.X 170 | minY := img.Bounds().Min.Y 171 | maxX := img.Bounds().Max.X 172 | maxY := img.Bounds().Max.Y 173 | for (maxX-minX)%height != 0 { 174 | maxX-- 175 | } 176 | for (maxY-minY)%width != 0 { 177 | maxY-- 178 | } 179 | scaleX := (maxX - minX) / height 180 | scaleY := (maxY - minY) / width 181 | imgRect := image.Rect(0, 0, height, width) 182 | resImg := image.NewRGBA(imgRect) 183 | draw.Draw(resImg, resImg.Bounds(), &image.Uniform{C: color.White}, image.ZP, draw.Src) 184 | for y := 0; y < width; y++ { 185 | for x := 0; x < height; x++ { 186 | averageColor := getAverageColor(img, minX+x*scaleX, minX+(x+1)*scaleX, minY+y*scaleY, minY+(y+1)*scaleY) 187 | resImg.Set(x, y, averageColor) 188 | } 189 | } 190 | return resImg 191 | } 192 | 193 | func getAverageColor(img image.Image, minX int, maxX int, minY int, maxY int) color.Color { 194 | var averageRed float64 195 | var averageGreen float64 196 | var averageBlue float64 197 | var averageAlpha float64 198 | scale := 1.0 / float64((maxX-minX)*(maxY-minY)) 199 | 200 | for i := minX; i < maxX; i++ { 201 | for k := minY; k < maxY; k++ { 202 | r, g, b, a := img.At(i, k).RGBA() 203 | averageRed += float64(r) * scale 204 | averageGreen += float64(g) * scale 205 | averageBlue += float64(b) * scale 206 | averageAlpha += float64(a) * scale 207 | } 208 | } 209 | averageRed = math.Sqrt(averageRed) 210 | averageGreen = math.Sqrt(averageGreen) 211 | averageBlue = math.Sqrt(averageBlue) 212 | averageAlpha = math.Sqrt(averageAlpha) 213 | averageColor := color.RGBA{R: uint8(averageRed), G: uint8(averageGreen), B: uint8(averageBlue), A: uint8(averageAlpha)} 214 | return averageColor 215 | } 216 | -------------------------------------------------------------------------------- /weights_darknet.go: -------------------------------------------------------------------------------- 1 | package yologo 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | // ParseConfiguration Parse darknet configuration file 12 | func ParseConfiguration(fname string) ([]map[string]string, error) { 13 | file, err := os.Open(fname) 14 | if err != nil { 15 | return nil, err 16 | } 17 | defer file.Close() 18 | lines := []string{} 19 | scanner := bufio.NewScanner(file) 20 | for scanner.Scan() { 21 | text := scanner.Text() 22 | if len(text) < 1 { 23 | continue 24 | } 25 | if text[0] == '#' { 26 | continue 27 | } 28 | lines = append(lines, strings.TrimSpace(text)) 29 | } 30 | if err := scanner.Err(); err != nil { 31 | return nil, err 32 | } 33 | block := make(map[string]string) 34 | blocks := []map[string]string{} 35 | for i := range lines { 36 | if lines[i][0] == '[' { 37 | if len(block) != 0 { 38 | blocks = append(blocks, block) 39 | block = make(map[string]string) 40 | } 41 | block["type"] = lines[i][1 : len(lines[i])-1] 42 | } else { 43 | kv := strings.Split(lines[i], "=") 44 | if len(kv) != 2 { 45 | return nil, fmt.Errorf("Wrong format of layer parameters: %s", lines[i]) 46 | } 47 | key, value := strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1]) 48 | block[key] = value 49 | } 50 | } 51 | blocks = append(blocks, block) 52 | return blocks, nil 53 | } 54 | 55 | // ParseWeights Parse darknet weights 56 | func ParseWeights(fname string) ([]float32, error) { 57 | fp, err := os.Open(fname) 58 | if err != nil { 59 | return nil, err 60 | } 61 | defer fp.Close() 62 | summary := []byte{} 63 | data := make([]byte, 4096) 64 | for { 65 | data = data[:cap(data)] 66 | n, err := fp.Read(data) 67 | if err != nil { 68 | if err == io.EOF { 69 | break 70 | } 71 | return nil, err 72 | } 73 | data = data[:n] 74 | summary = append(summary, data...) 75 | } 76 | dataF32 := []float32{} 77 | for i := 0; i < len(summary); i += 4 { 78 | tempSlice := summary[i : i+4] 79 | tempFloat32 := Float32frombytes(tempSlice) 80 | dataF32 = append(dataF32, tempFloat32) 81 | } 82 | return dataF32, nil 83 | } 84 | -------------------------------------------------------------------------------- /yolo_diff_op.go: -------------------------------------------------------------------------------- 1 | package yologo 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/chewxy/hm" 7 | "github.com/pkg/errors" 8 | "gorgonia.org/gorgonia" 9 | "gorgonia.org/tensor" 10 | ) 11 | 12 | // Wrap yoloOp 13 | type yoloDiffOp struct { 14 | yoloOp 15 | } 16 | 17 | /* Methods to match gorgonia.Op interface */ 18 | func (op *yoloDiffOp) Arity() int { return 2 } 19 | func (op *yoloDiffOp) Type() hm.Type { 20 | a := hm.TypeVariable('a') 21 | t := gorgonia.TensorType{Dims: 4, Of: a} 22 | o := gorgonia.TensorType{Dims: 3, Of: a} 23 | return hm.NewFnType(t, o, t) 24 | } 25 | 26 | func (op *yoloDiffOp) ReturnsPtr() bool { return true } 27 | func (op *yoloDiffOp) CallsExtern() bool { return false } 28 | func (op *yoloDiffOp) OverwritesInput() int { return -1 } 29 | func (op *yoloDiffOp) InferShape(inputs ...gorgonia.DimSizer) (tensor.Shape, error) { 30 | s := inputs[0].(tensor.Shape).Clone() 31 | return s, nil 32 | } 33 | func (op *yoloDiffOp) Do(inputs ...gorgonia.Value) (gorgonia.Value, error) { 34 | if op.training == nil { 35 | return nil, fmt.Errorf("Training parameters for yoloOp were not set") 36 | } 37 | if op.training.inputs == nil { 38 | return nil, fmt.Errorf("Training parameter 'inputs' for yoloOp were not set") 39 | } 40 | if op.training.scales == nil { 41 | return nil, fmt.Errorf("Training parameter 'scales' for yoloOp were not set") 42 | } 43 | if op.training.targets == nil { 44 | return nil, fmt.Errorf("Training parameter 'targets' for yoloOp were not set") 45 | } 46 | if op.training.bboxes == nil { 47 | return nil, fmt.Errorf("Training parameter 'bboxes' for yoloOp were not set") 48 | } 49 | 50 | in := inputs[0] 51 | output := inputs[1] 52 | inGrad := tensor.New(tensor.Of(in.Dtype()), tensor.WithShape(output.Shape().Clone()...), tensor.WithEngine(in.(tensor.Tensor).Engine())) 53 | switch in.Dtype() { 54 | case tensor.Float32: 55 | inGradData := inGrad.Data().([]float32) 56 | outGradData := output.Data().([]float32) 57 | op.f32(inGradData, outGradData, op.training.scales, op.training.inputs, op.training.targets, op.training.bboxes) 58 | break 59 | case tensor.Float64: 60 | return nil, fmt.Errorf("yoloDiffOp for Float64 is not implemented yet") 61 | default: 62 | return nil, fmt.Errorf("yoloDiffOp supports only Float32/Float64 types") 63 | } 64 | 65 | err := inGrad.Reshape(1, op.gridSize*op.gridSize, (op.numClasses+5)*len(op.masks)) 66 | if err != nil { 67 | return nil, errors.Wrap(err, "Can't reshape in yoloDiffOp (1)") 68 | } 69 | err = inGrad.T(0, 2, 1) 70 | if err != nil { 71 | return nil, errors.Wrap(err, "Can't safely transponse in yoloDiffOp (1)") 72 | } 73 | err = inGrad.Transpose() 74 | if err != nil { 75 | return nil, errors.Wrap(err, "Can't transponse in yoloDiffOp (1)") 76 | } 77 | err = inGrad.Reshape(1, len(op.masks)*(5+op.numClasses), op.gridSize, op.gridSize) 78 | if err != nil { 79 | return nil, errors.Wrap(err, "Can't reshape in yoloDiffOp (2)") 80 | } 81 | return inGrad, nil 82 | } 83 | 84 | func (op *yoloOp) DoDiff(ctx gorgonia.ExecutionContext, inputs gorgonia.Nodes, output *gorgonia.Node) (err error) { 85 | return fmt.Errorf("DoDiff for yoloOp is not implemented") 86 | } 87 | func (op *yoloOp) DiffWRT(inputs int) []bool { return []bool{true} } 88 | func (op *yoloOp) SymDiff(inputs gorgonia.Nodes, output, grad *gorgonia.Node) (retVal gorgonia.Nodes, err error) { 89 | if err = checkArity(op, len(inputs)); err != nil { 90 | return 91 | } 92 | in := inputs[0] 93 | var op2 yoloOp 94 | op2 = *op 95 | diff := &yoloDiffOp{op2} 96 | 97 | var ret *gorgonia.Node 98 | if ret, err = gorgonia.ApplyOp(diff, in, grad); err != nil { 99 | return nil, err 100 | } 101 | return gorgonia.Nodes{ret}, nil 102 | } 103 | 104 | /* Unexported methods */ 105 | 106 | func (op *yoloDiffOp) f32(inGradData, outGradData, scales, inputs, targets, bboxes []float32) { 107 | for i := range inGradData { 108 | inGradData[i] = 0 109 | } 110 | for i := 0; i < len(outGradData); i = i + 5 + op.numClasses { 111 | for j := 0; j < 4; j++ { 112 | inGradData[i+j] = outGradData[i+j] * (scales[i+j] * scales[i+j] * (inputs[i+j] - targets[i+j])) 113 | } 114 | for j := 4; j < 5+op.numClasses; j++ { 115 | if outGradData[i+j] != 0 { 116 | if targets[i+j] == 0 { 117 | inGradData[i+j] = outGradData[i+j] * (bboxes[i+j]) 118 | } else { 119 | inGradData[i+j] = outGradData[i+j] * (1 - bboxes[i+j]) 120 | } 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /yolo_layer.go: -------------------------------------------------------------------------------- 1 | package yologo 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pkg/errors" 7 | "gorgonia.org/gorgonia" 8 | ) 9 | 10 | type yoloLayer struct { 11 | masks []int 12 | anchors [][2]int 13 | flattenAnchors []int 14 | inputSize int 15 | classesNum int 16 | ignoreThresh float32 17 | 18 | yoloTrainer YoloTrainer 19 | } 20 | 21 | func (l *yoloLayer) String() string { 22 | str := "YOLO layer: " 23 | for m := range l.masks { 24 | str += fmt.Sprintf("Mask->%[1]d Anchors->[%[2]d, %[3]d]", l.masks[m], l.anchors[m][0], l.anchors[m][1]) 25 | if m != len(l.masks)-1 { 26 | str += "\t|\t" 27 | } 28 | } 29 | return str 30 | } 31 | 32 | func (l *yoloLayer) Type() string { 33 | return "yolo" 34 | } 35 | 36 | func (l *yoloLayer) ToNode(g *gorgonia.ExprGraph, input ...*gorgonia.Node) (*gorgonia.Node, error) { 37 | inputN := input[0] 38 | if len(inputN.Shape()) == 0 { 39 | return nil, fmt.Errorf("Input shape for YOLO layer is nil") 40 | } 41 | flattenAnchorsF32 := make([]float32, len(l.flattenAnchors)) 42 | for i := range flattenAnchorsF32 { 43 | flattenAnchorsF32[i] = float32(l.flattenAnchors[i]) 44 | } 45 | masksIdx := make([]int, len(l.masks)) 46 | for i := range l.masks { 47 | masksIdx[i] = i 48 | } 49 | yoloNode, yoloTrainer, err := YOLOv3Node(inputN, flattenAnchorsF32, masksIdx, l.inputSize, l.classesNum, l.ignoreThresh) 50 | l.yoloTrainer = yoloTrainer 51 | if err != nil { 52 | return nil, errors.Wrap(err, "Can't prepare YOLOv3 operation") 53 | } 54 | 55 | return yoloNode, nil 56 | } 57 | -------------------------------------------------------------------------------- /yolo_op.go: -------------------------------------------------------------------------------- 1 | package yologo 2 | 3 | import ( 4 | "fmt" 5 | "hash" 6 | "hash/fnv" 7 | 8 | "github.com/chewxy/hm" 9 | "github.com/chewxy/math32" 10 | "github.com/pkg/errors" 11 | "gorgonia.org/gorgonia" 12 | "gorgonia.org/tensor" 13 | ) 14 | 15 | type yoloOp struct { 16 | anchors []float32 17 | masks []int 18 | ignoreTresh float32 19 | dimensions int 20 | numClasses int 21 | 22 | trainMode bool 23 | gridSize int 24 | bestAnchors [][]int 25 | training *yoloTraining 26 | } 27 | 28 | func newYoloOp(anchors []float32, masks []int, netSize, gridSize, numClasses int, ignoreTresh float32) *yoloOp { 29 | yoloOp := &yoloOp{ 30 | anchors: anchors, 31 | dimensions: netSize, 32 | numClasses: numClasses, 33 | ignoreTresh: ignoreTresh, 34 | masks: masks, 35 | trainMode: false, 36 | gridSize: gridSize, 37 | training: &yoloTraining{}, 38 | } 39 | return yoloOp 40 | } 41 | 42 | // YOLOv3Node Constructor for YOLO-based node operation 43 | /* 44 | input - Input node 45 | anchors - Slice of anchors 46 | masks - Slice of masks 47 | netSize - Height/Width of input 48 | numClasses - Amount of classes 49 | ignoreTresh - Treshold 50 | targets - Desired targets. 51 | */ 52 | func YOLOv3Node(input *gorgonia.Node, anchors []float32, masks []int, netSize, numClasses int, ignoreTresh float32, targets ...*gorgonia.Node) (*gorgonia.Node, YoloTrainer, error) { 53 | // @todo: need to check input.Shape()[2] accessibility 54 | op := newYoloOp(anchors, masks, netSize, input.Shape()[2], numClasses, ignoreTresh) 55 | ret, err := gorgonia.ApplyOp(op, input) 56 | return ret, op, err 57 | } 58 | 59 | /* Methods to match gorgonia.Op interface */ 60 | 61 | func (op *yoloOp) Arity() int { 62 | return 1 63 | } 64 | func (op *yoloOp) ReturnsPtr() bool { return false } 65 | func (op *yoloOp) CallsExtern() bool { return false } 66 | func (op *yoloOp) WriteHash(h hash.Hash) { 67 | fmt.Fprintf(h, "YOLO{}(anchors: (%v))", op.anchors) 68 | } 69 | func (op *yoloOp) Hashcode() uint32 { 70 | h := fnv.New32a() 71 | op.WriteHash(h) 72 | return h.Sum32() 73 | } 74 | func (op *yoloOp) String() string { 75 | return fmt.Sprintf("YOLO{}(anchors: (%v))", op.anchors) 76 | } 77 | func (op *yoloOp) InferShape(inputs ...gorgonia.DimSizer) (tensor.Shape, error) { 78 | shp := inputs[0].(tensor.Shape) 79 | if len(shp) < 4 { 80 | return nil, fmt.Errorf("InferShape() for YOLO must contain 4 dimensions, but recieved %d)", len(shp)) 81 | } 82 | s := shp.Clone() 83 | if op.trainMode { 84 | return []int{s[0], s[2] * s[3] * len(op.masks), (s[1] - 1) / len(op.masks)}, nil 85 | } 86 | return []int{s[0], s[2] * s[3] * len(op.masks), s[1] / len(op.masks)}, nil 87 | } 88 | func (op *yoloOp) Type() hm.Type { 89 | a := hm.TypeVariable('a') 90 | t := gorgonia.TensorType{Dims: 4, Of: a} 91 | o := gorgonia.TensorType{Dims: 3, Of: a} 92 | return hm.NewFnType(t, o) 93 | } 94 | func (op *yoloOp) OverwritesInput() int { 95 | return -1 96 | } 97 | 98 | func (op *yoloOp) Do(inputs ...gorgonia.Value) (retVal gorgonia.Value, err error) { 99 | inputTensor, err := op.checkInput(inputs...) 100 | if err != nil { 101 | return nil, errors.Wrap(err, "Can't check YOLO input") 102 | } 103 | batchSize := inputTensor.Shape()[0] 104 | stride := op.dimensions / inputTensor.Shape()[2] 105 | gridSize := inputTensor.Shape()[2] 106 | bboxAttributes := 5 + op.numClasses 107 | numAnchors := len(op.anchors) / 2 108 | currentAnchors := []float32{} 109 | for i := range op.masks { 110 | if op.masks[i] >= numAnchors { 111 | return nil, fmt.Errorf("Num of anchors is %[1]d, but mask values is %[2]d > %[1]d", numAnchors, op.masks[i]) 112 | } 113 | currentAnchors = append(currentAnchors, op.anchors[i*2], op.anchors[i*2+1]) 114 | } 115 | 116 | // Prepare reshaped input (it's common for both training and detection mode) 117 | err = prepareReshapedInput(inputTensor, batchSize, gridSize, bboxAttributes, numAnchors) 118 | if err != nil { 119 | return nil, errors.Wrap(err, "Can't prepare reshaped input") 120 | } 121 | 122 | // Just inference without backpropagation in case of detection mode 123 | if !op.trainMode { 124 | return op.evaluateYOLOF32(inputTensor, batchSize, stride, gridSize, bboxAttributes, len(op.masks), currentAnchors) 125 | } 126 | 127 | // Training mode 128 | inputTensorCopy := inputTensor.Clone().(tensor.Tensor) 129 | var yoloBBoxes tensor.Tensor 130 | yoloBBoxes, err = op.evaluateYOLOF32(inputTensorCopy, batchSize, stride, gridSize, bboxAttributes, len(op.masks), currentAnchors) 131 | if err != nil { 132 | return nil, errors.Wrap(err, "Can't evaluate YOLO [Training mode]") 133 | } 134 | if op.training == nil { 135 | return nil, fmt.Errorf("Nil pointer on training params in yoloOp [Training mode]") 136 | } 137 | op.training.inputs, err = tensorToF32(inputTensor) 138 | if err != nil { 139 | return nil, errors.Wrap(err, "Can't cast tensor to []float32 for inputs [Training mode]") 140 | } 141 | op.training.bboxes, err = tensorToF32(yoloBBoxes) 142 | if err != nil { 143 | return nil, errors.Wrap(err, "Can't cast tensor to []float32 for bboxes [Training mode]") 144 | } 145 | 146 | preparedYOLOout := prepareTrainingOutputF32( 147 | op.training.inputs, op.training.bboxes, 148 | op.training.targets, op.training.scales, 149 | op.bestAnchors, op.masks, 150 | op.numClasses, op.dimensions, op.gridSize, op.ignoreTresh, 151 | ) 152 | 153 | yoloTrainingTensor := tensor.New(tensor.WithShape(1, op.gridSize*op.gridSize*len(op.masks), 5+op.numClasses), tensor.Of(tensor.Float32), tensor.WithBacking(preparedYOLOout)) 154 | 155 | return yoloTrainingTensor, nil 156 | } 157 | 158 | /* Unexported methods */ 159 | 160 | // checkInput Check if everything is OK with inputs and returns tensor.Tensor (if OK) 161 | func (op *yoloOp) checkInput(inputs ...gorgonia.Value) (tensor.Tensor, error) { 162 | if err := checkArity(op, len(inputs)); err != nil { 163 | return nil, errors.Wrap(err, "Can't check arity") 164 | } 165 | input := inputs[0] 166 | inputTensor := input.(tensor.Tensor) 167 | shp := inputTensor.Shape().Dims() 168 | if shp != 4 { 169 | return nil, fmt.Errorf("YOLO must contain 4 dimensions, but recieved %d)", shp) 170 | } 171 | inputNumericType := input.Dtype() 172 | switch inputNumericType { 173 | case gorgonia.Float32: 174 | return inputTensor, nil 175 | default: 176 | return nil, fmt.Errorf("Only Float32 supported for inputs, but got %v", inputNumericType) 177 | } 178 | } 179 | 180 | func prepareReshapedInput(input tensor.Tensor, batchSize, grid, bboxAttrs, numAnchors int) error { 181 | err := input.Reshape(batchSize, bboxAttrs*numAnchors, grid*grid) 182 | if err != nil { 183 | return errors.Wrap(err, "Can't make reshape grid^2 for YOLO") 184 | } 185 | err = input.T(0, 2, 1) 186 | if err != nil { 187 | return errors.Wrap(err, "Can't safely transponse input for YOLO") 188 | } 189 | err = input.Transpose() 190 | if err != nil { 191 | return errors.Wrap(err, "Can't transponse input for YOLO") 192 | } 193 | err = input.Reshape(batchSize, grid*grid*numAnchors, bboxAttrs) 194 | if err != nil { 195 | return errors.Wrap(err, "Can't reshape bbox for YOLO") 196 | } 197 | return nil 198 | } 199 | 200 | func (op *yoloOp) evaluateYOLOF32(input tensor.Tensor, batchSize, stride, grid, bboxAttrs, numAnchors int, currentAnchors []float32) (retVal tensor.Tensor, err error) { 201 | 202 | // Activation of x, y via sigmoid function 203 | slXY, err := input.Slice(nil, nil, Slice(0, 2)) 204 | _, err = slXY.Apply(_sigmoidf32, tensor.UseUnsafe()) 205 | if err != nil { 206 | return nil, errors.Wrap(err, "Can't activate XY") 207 | } 208 | 209 | // Activation of classes (objects) via sigmoid function 210 | slClasses, err := input.Slice(nil, nil, Slice(4, 5+op.numClasses)) 211 | _, err = slClasses.Apply(_sigmoidf32, tensor.UseUnsafe()) 212 | if err != nil { 213 | return nil, errors.Wrap(err, "Can't activate classes") 214 | } 215 | 216 | step := grid * numAnchors 217 | for i := 0; i < grid; i++ { 218 | 219 | vy, err := input.Slice(nil, Slice(i*step, i*step+step), Slice(1)) 220 | if err != nil { 221 | return nil, errors.Wrap(err, "Can't slice while doing steps for grid") 222 | } 223 | 224 | _, err = tensor.Add(vy, float32(i), tensor.UseUnsafe()) 225 | if err != nil { 226 | return nil, errors.Wrap(err, "Can't do tensor.Add(...) for float32; (1)") 227 | } 228 | 229 | for n := 0; n < numAnchors; n++ { 230 | anchorsSlice, err := input.Slice(nil, Slice(i*numAnchors+n, input.Shape()[1], step), Slice(0)) 231 | if err != nil { 232 | return nil, errors.Wrap(err, "Can't slice anchors while doing steps for grid") 233 | } 234 | _, err = tensor.Add(anchorsSlice, float32(i), tensor.UseUnsafe()) 235 | if err != nil { 236 | return nil, errors.Wrap(err, "Can't do tensor.Add(...) for float32; (1)") 237 | } 238 | } 239 | 240 | } 241 | 242 | anchors := []float32{} 243 | for i := 0; i < grid*grid; i++ { 244 | anchors = append(anchors, currentAnchors...) 245 | } 246 | 247 | anchorsTensor := tensor.New(tensor.Of(tensor.Float32), tensor.WithShape(1, grid*grid*numAnchors, 2)) 248 | for i := range anchors { 249 | anchorsTensor.Set(i, anchors[i]) 250 | } 251 | 252 | _, err = tensor.Div(anchorsTensor, float32(stride), tensor.UseUnsafe()) 253 | if err != nil { 254 | return nil, errors.Wrap(err, "Can't do tensor.Div(...) for float32") 255 | } 256 | 257 | vhw, err := input.Slice(nil, nil, Slice(2, 4)) 258 | if err != nil { 259 | return nil, errors.Wrap(err, "Can't do slice on input S(2,4)") 260 | } 261 | 262 | _, err = vhw.Apply(math32.Exp, tensor.UseUnsafe()) 263 | if err != nil { 264 | return nil, errors.Wrap(err, "Can't apply exp32 to YOLO operation") 265 | } 266 | 267 | _, err = tensor.Mul(vhw, anchorsTensor, tensor.UseUnsafe()) 268 | if err != nil { 269 | return nil, errors.Wrap(err, "Can't do tensor.Mul(...) for anchors") 270 | } 271 | 272 | vv, err := input.Slice(nil, nil, Slice(0, 4)) 273 | if err != nil { 274 | return nil, errors.Wrap(err, "Can't do slice on input S(0,4)") 275 | } 276 | 277 | _, err = tensor.Mul(vv, float32(stride), tensor.UseUnsafe()) 278 | if err != nil { 279 | return nil, errors.Wrap(err, "Can't do tensor.Mul(...) for float32") 280 | } 281 | 282 | return input, nil 283 | } 284 | -------------------------------------------------------------------------------- /yolo_op_test.go: -------------------------------------------------------------------------------- 1 | package yologo 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "gorgonia.org/gorgonia" 10 | "gorgonia.org/tensor" 11 | ) 12 | 13 | func TestYolo(t *testing.T) { 14 | 15 | inputSize := 416 16 | numClasses := 80 17 | testAnchors := [][]float32{ 18 | []float32{10, 13, 16, 30, 33, 23}, 19 | []float32{30, 51, 62, 45, 59, 119}, 20 | []float32{116, 90, 156, 198, 373, 326}, 21 | } 22 | 23 | numpyInputs := []string{ 24 | "./test_yolo_op_data/1input.[(10, 13), (16, 30), (33, 23)].npy", 25 | "./test_yolo_op_data/1input.[(30, 61), (62, 45), (59, 119)].npy", 26 | "./test_yolo_op_data/1input.[(116, 90), (156, 198), (373, 326)].npy", 27 | } 28 | 29 | numpyExpectedOutputs := []string{ 30 | "./test_yolo_op_data/1output.[(10, 13), (16, 30), (33, 23)].npy", 31 | "./test_yolo_op_data/1output.[(30, 61), (62, 45), (59, 119)].npy", 32 | "./test_yolo_op_data/1output.[(116, 90), (156, 198), (373, 326)].npy", 33 | } 34 | 35 | for i := range testAnchors { 36 | // Read input values from numpy format 37 | input := tensor.New(tensor.Of(tensor.Float32)) 38 | r, err := os.Open(numpyInputs[i]) 39 | if err != nil { 40 | t.Error(err) 41 | return 42 | } 43 | err = input.ReadNpy(r) 44 | if err != nil { 45 | t.Error(err) 46 | return 47 | } 48 | 49 | // Read expected values from numpy format 50 | expected := tensor.New(tensor.Of(tensor.Float32)) 51 | r, err = os.Open(numpyExpectedOutputs[i]) 52 | if err != nil { 53 | t.Error(err) 54 | return 55 | } 56 | err = expected.ReadNpy(r) 57 | if err != nil { 58 | t.Error(err) 59 | return 60 | } 61 | 62 | // Load graph 63 | g := gorgonia.NewGraph() 64 | inputTensor := gorgonia.NewTensor(g, tensor.Float32, 4, gorgonia.WithShape(input.Shape()...), gorgonia.WithName("yolo")) 65 | // Prepare YOLOv3 node 66 | outNode, _, err := YOLOv3Node(inputTensor, testAnchors[i], []int{0, 1, 2}, inputSize, numClasses, 0.7) 67 | if err != nil { 68 | t.Error(err) 69 | return 70 | } 71 | // Run operation 72 | vm := gorgonia.NewTapeMachine(g) 73 | if err := gorgonia.Let(inputTensor, input); err != nil { 74 | t.Error(err) 75 | return 76 | } 77 | vm.RunAll() 78 | vm.Close() 79 | 80 | // Check if everything is fine 81 | if !assert.Equal(t, outNode.Value().Data(), expected.Data(), "Output is not equal to expected value") { 82 | t.Error(fmt.Sprintf("Got: %v\nExpected: %v", outNode.Value(), expected)) 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /yolo_trainer.go: -------------------------------------------------------------------------------- 1 | package yologo 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/chewxy/math32" 7 | ) 8 | 9 | // YoloTrainer Wrapper around yoloOP 10 | // It has method for setting desired bboxes as output of network 11 | type YoloTrainer interface { 12 | ActivateTrainingMode() 13 | DisableTrainingMode() 14 | SetTarget([]float32) 15 | } 16 | 17 | type yoloTraining struct { 18 | inputs []float32 19 | bboxes []float32 20 | scales []float32 21 | targets []float32 22 | } 23 | 24 | // ActivateTrainingMode Activates training mode for yoloOP 25 | func (op *yoloOp) ActivateTrainingMode() { 26 | op.trainMode = true 27 | } 28 | 29 | // DisableTrainingMode Disables training mode for yoloOP 30 | func (op *yoloOp) DisableTrainingMode() { 31 | op.trainMode = false 32 | } 33 | 34 | // SetTarget sets []float32 as desired target for yoloOP 35 | func (op *yoloOp) SetTarget(target []float32) { 36 | preparedNumOfElements := op.gridSize * op.gridSize * len(op.masks) * (5 + op.numClasses) 37 | if op.training == nil { 38 | fmt.Println("Training parameters were not set. Initializing empty slices....") 39 | op.training = &yoloTraining{} 40 | } 41 | op.training.scales = make([]float32, preparedNumOfElements) 42 | op.training.targets = make([]float32, preparedNumOfElements) 43 | for i := range op.training.scales { 44 | op.training.scales[i] = 1 45 | } 46 | 47 | gridSizeF32 := float32(op.gridSize) 48 | op.bestAnchors = getBestAnchorsF32(target, op.anchors, op.masks, op.dimensions, gridSizeF32) 49 | for i := 0; i < len(op.bestAnchors); i++ { 50 | scale := (2 - target[i*5+3]*target[i*5+4]) 51 | giInt := op.bestAnchors[i][1] 52 | gjInt := op.bestAnchors[i][2] 53 | gx := invsigmF32(target[i*5+1]*gridSizeF32 - float32(giInt)) 54 | gy := invsigmF32(target[i*5+2]*gridSizeF32 - float32(gjInt)) 55 | bestAnchor := op.masks[op.bestAnchors[i][0]] * 2 56 | gw := math32.Log(target[i*5+3]/op.anchors[bestAnchor] + 1e-16) 57 | gh := math32.Log(target[i*5+4]/op.anchors[bestAnchor+1] + 1e-16) 58 | bboxIdx := gjInt*op.gridSize*(5+op.numClasses)*len(op.masks) + giInt*(5+op.numClasses)*len(op.masks) + op.bestAnchors[i][0]*(5+op.numClasses) 59 | op.training.scales[bboxIdx] = scale 60 | op.training.targets[bboxIdx] = gx 61 | op.training.scales[bboxIdx+1] = scale 62 | op.training.targets[bboxIdx+1] = gy 63 | op.training.scales[bboxIdx+2] = scale 64 | op.training.targets[bboxIdx+2] = gw 65 | op.training.scales[bboxIdx+3] = scale 66 | op.training.targets[bboxIdx+3] = gh 67 | op.training.targets[bboxIdx+4] = 1 68 | for j := 0; j < op.numClasses; j++ { 69 | if j == int(target[i*5]) { 70 | op.training.targets[bboxIdx+5+j] = 1 71 | } 72 | } 73 | } 74 | } 75 | 76 | func getBestAnchorsF32(target []float32, anchors []float32, masks []int, dims int, gridSize float32) [][]int { 77 | bestAnchors := make([][]int, len(target)/5) 78 | imgsize := float32(dims) 79 | for j := 0; j < len(target); j = j + 5 { 80 | targetRect := rectifyBoxF32(0, 0, target[j+3]*imgsize, target[j+4]*imgsize, dims) //not absolutely confident in rectangle sizes 81 | bestIOU := float32(0.0) 82 | bestAnchors[j/5] = make([]int, 3) 83 | for i := 0; i < len(anchors); i = i + 2 { 84 | anchorRect := rectifyBoxF32(0, 0, anchors[i], anchors[i+1], dims) 85 | currentIOU := IOUFloat32(anchorRect, targetRect) 86 | if currentIOU >= bestIOU { 87 | bestAnchors[j/5][0] = i 88 | bestIOU = currentIOU 89 | } 90 | } 91 | bestAnchors[j/5][0] = findIntElement(masks, bestAnchors[j/5][0]/2) 92 | if bestAnchors[j/5][0] != -1 { 93 | bestAnchors[j/5][1] = int(target[j+1] * gridSize) 94 | bestAnchors[j/5][2] = int(target[j+2] * gridSize) 95 | } 96 | } 97 | return bestAnchors 98 | } 99 | 100 | func prepareTrainingOutputF32(input, yoloBoxes, target, scales []float32, bestAnchors [][]int, masks []int, numClasses, dims, gridSize int, ignoreTresh float32) []float32 { 101 | yoloBBoxes := make([]float32, len(yoloBoxes)) 102 | bestIous := getBestIOUF32(yoloBoxes, target, numClasses, dims) 103 | for i := 0; i < len(yoloBoxes); i = i + (5 + numClasses) { 104 | if bestIous[i/(5+numClasses)][0] <= ignoreTresh { 105 | yoloBBoxes[i+4] = bceLossF32(0, yoloBoxes[i+4]) 106 | } 107 | } 108 | for i := 0; i < len(bestAnchors); i++ { 109 | if bestAnchors[i][0] != -1 { 110 | giInt := bestAnchors[i][1] 111 | gjInt := bestAnchors[i][2] 112 | boxi := gjInt*gridSize*(5+numClasses)*len(masks) + giInt*(5+numClasses)*len(masks) + bestAnchors[i][0]*(5+numClasses) 113 | yoloBBoxes[boxi] = mseLossF32(target[boxi], input[boxi], scales[boxi]) 114 | yoloBBoxes[boxi+1] = mseLossF32(target[boxi+1], input[boxi+1], scales[boxi+1]) 115 | yoloBBoxes[boxi+2] = mseLossF32(target[boxi+2], input[boxi+2], scales[boxi+2]) 116 | yoloBBoxes[boxi+3] = mseLossF32(target[boxi+3], input[boxi+3], scales[boxi+3]) 117 | for j := 0; j < numClasses+1; j++ { 118 | yoloBBoxes[boxi+4+j] = bceLossF32(target[boxi+4+j], yoloBoxes[boxi+4+j]) 119 | } 120 | } 121 | } 122 | return yoloBBoxes 123 | } 124 | 125 | func invsigmF32(target float32) float32 { 126 | return -math32.Log(1-target+1e-16) + math32.Log(target+1e-16) 127 | } 128 | 129 | func bceLossF32(target, pred float32) float32 { 130 | if target == 1.0 { 131 | return -(math32.Log(pred + 1e-16)) 132 | } 133 | return -(math32.Log((1.0 - pred) + 1e-16)) 134 | } 135 | 136 | func mseLossF32(target, pred, scale float32) float32 { 137 | return math32.Pow(scale*(target-pred), 2) / 2.0 138 | } 139 | -------------------------------------------------------------------------------- /yolov3.go: -------------------------------------------------------------------------------- 1 | package yologo 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/chewxy/math32" 9 | "github.com/pkg/errors" 10 | "gorgonia.org/gorgonia" 11 | "gorgonia.org/tensor" 12 | ) 13 | 14 | // YOLOv3 YOLOv3 architecture 15 | type YOLOv3 struct { 16 | g *gorgonia.ExprGraph 17 | classesNum, boxesPerCell, netSize int 18 | out []*gorgonia.Node 19 | layersInfo []string 20 | 21 | LearningNodes []*gorgonia.Node 22 | training []YoloTrainer 23 | } 24 | 25 | // Print Print architecture of network 26 | func (net *YOLOv3) Print() { 27 | for i := range net.layersInfo { 28 | fmt.Println(net.layersInfo[i]) 29 | } 30 | } 31 | 32 | // GetOutput Get out YOLO layers (can be multiple of them) 33 | func (net *YOLOv3) GetOutput() []*gorgonia.Node { 34 | return net.out 35 | } 36 | 37 | // NewYoloV3 Create new YOLO v3 38 | func NewYoloV3(g *gorgonia.ExprGraph, input *gorgonia.Node, classesNumber, boxesPerCell int, leakyCoef float64, cfgFile, weightsFile string) (*YOLOv3, error) { 39 | shp := input.Shape() 40 | if len(shp) < 4 { 41 | return nil, fmt.Errorf("Input for tiny-YOLOv3 must contain 4 dimensions, but recieved %d)", len(shp)) 42 | } 43 | 44 | buildingBlocks, err := ParseConfiguration(cfgFile) 45 | if err != nil { 46 | return nil, errors.Wrap(err, "Can't read darknet configuration") 47 | } 48 | 49 | netParams := buildingBlocks[0] 50 | netWidthStr := netParams["width"] 51 | netWidth, err := strconv.Atoi(netWidthStr) 52 | if err != nil { 53 | return nil, errors.Wrap(err, fmt.Sprintf("Network's width must be integer, got value: '%s'", netWidthStr)) 54 | } 55 | 56 | weightsData, err := ParseWeights(weightsFile) 57 | if err != nil { 58 | return nil, errors.Wrap(err, "Can't read darknet weights") 59 | } 60 | 61 | fmt.Println("Loading network...") 62 | layers := []*layerN{} 63 | outputFilters := []int{} 64 | prevFilters := 3 65 | 66 | networkNodes := []*gorgonia.Node{} 67 | 68 | blocks := buildingBlocks[1:] 69 | lastIdx := 5 // Skip first 5 values (header of weights file) 70 | epsilon := float32(0.000001) 71 | 72 | yoloNodes := []*gorgonia.Node{} 73 | learningNodes := []*gorgonia.Node{} 74 | yoloTrainers := []YoloTrainer{} 75 | 76 | for i := range blocks { 77 | block := blocks[i] 78 | filtersIdx := 0 79 | layerType, ok := block["type"] 80 | if ok { 81 | switch layerType { 82 | case "convolutional": 83 | filters := 0 84 | padding := 0 85 | kernelSize := 0 86 | stride := 0 87 | batchNormalize := 0 88 | bias := false 89 | activation := "activation" 90 | activation, ok := block["activation"] 91 | if !ok { 92 | fmt.Printf("No field 'activation' for convolution layer") 93 | continue 94 | } 95 | batchNormalizeStr, ok := block["batch_normalize"] 96 | batchNormalize, err := strconv.Atoi(batchNormalizeStr) 97 | if !ok || err != nil { 98 | batchNormalize = 0 99 | bias = true 100 | } 101 | filtersStr, ok := block["filters"] 102 | filters, err = strconv.Atoi(filtersStr) 103 | if !ok || err != nil { 104 | fmt.Printf("Wrong or empty 'filters' parameter for convolution layer: %s\n", err.Error()) 105 | continue 106 | } 107 | paddingStr, ok := block["pad"] 108 | padding, err = strconv.Atoi(paddingStr) 109 | if !ok || err != nil { 110 | fmt.Printf("Wrong or empty 'pad' parameter for convolution layer: %s\n", err.Error()) 111 | continue 112 | } 113 | kernelSizeStr, ok := block["size"] 114 | kernelSize, err = strconv.Atoi(kernelSizeStr) 115 | if !ok || err != nil { 116 | fmt.Printf("Wrong or empty 'size' parameter for convolution layer: %s\n", err.Error()) 117 | continue 118 | } 119 | pad := 0 120 | if padding != 0 { 121 | pad = (kernelSize - 1) / 2 122 | } 123 | strideStr, ok := block["stride"] 124 | stride, err = strconv.Atoi(strideStr) 125 | if !ok || err != nil { 126 | fmt.Printf("Wrong or empty 'stride' parameter for convolution layer: %s\n", err.Error()) 127 | continue 128 | } 129 | 130 | ll := &convLayer{ 131 | filters: filters, 132 | padding: pad, 133 | kernelSize: kernelSize, 134 | stride: stride, 135 | activation: activation, 136 | activationReLUCoef: leakyCoef, 137 | batchNormalize: batchNormalize, 138 | bias: bias, 139 | } 140 | 141 | shp := tensor.Shape{filters, prevFilters, kernelSize, kernelSize} 142 | kernels := []float32{} 143 | biases := []float32{} 144 | if ll.batchNormalize > 0 { 145 | nb := shp[0] 146 | nk := shp.TotalSize() 147 | 148 | biases = weightsData[lastIdx : lastIdx+nb] 149 | lastIdx += nb 150 | 151 | gammas := weightsData[lastIdx : lastIdx+nb] 152 | lastIdx += nb 153 | 154 | means := weightsData[lastIdx : lastIdx+nb] 155 | lastIdx += nb 156 | 157 | vars := weightsData[lastIdx : lastIdx+nb] 158 | lastIdx += nb 159 | 160 | kernels = weightsData[lastIdx : lastIdx+nk] 161 | lastIdx += nk 162 | 163 | // Denormalize weights 164 | for s := 0; s < shp[0]; s++ { 165 | scale := gammas[s] / math32.Sqrt(vars[s]+epsilon) 166 | biases[s] = biases[s] - means[s]*scale 167 | isize := shp[1] * shp[2] * shp[3] 168 | for j := 0; j < isize; j++ { 169 | kernels[isize*s+j] *= scale 170 | } 171 | } 172 | } else { 173 | if ll.bias { 174 | nb := shp[0] 175 | nk := shp.TotalSize() 176 | biases = weightsData[lastIdx : lastIdx+nb] 177 | lastIdx += nb 178 | kernels = weightsData[lastIdx : lastIdx+nk] 179 | lastIdx += nk 180 | } 181 | } 182 | 183 | convTensor := tensor.New(tensor.WithBacking(kernels), tensor.WithShape(shp...)) 184 | convNode := gorgonia.NewTensor(g, tensor.Float32, 4, gorgonia.WithShape(shp...), gorgonia.WithName(fmt.Sprintf("conv_%d", i)), gorgonia.WithValue(convTensor)) 185 | ll.convNode = convNode 186 | ll.biases = biases 187 | ll.layerIndex = i 188 | 189 | var l layerN = ll 190 | convBlock, err := l.ToNode(g, input) 191 | if err != nil { 192 | fmt.Printf("\tError preparing Convolutional block: %s\n", err.Error()) 193 | } 194 | networkNodes = append(networkNodes, convBlock) 195 | input = convBlock 196 | 197 | layers = append(layers, &l) 198 | learningNodes = append(learningNodes, ll.convNode) 199 | 200 | filtersIdx = filters 201 | break 202 | case "upsample": 203 | scale := 0 204 | scaleStr, ok := block["stride"] 205 | scale, err = strconv.Atoi(scaleStr) 206 | if !ok || err != nil { 207 | fmt.Printf("Wrong or empty 'stride' parameter for upsampling layer: %s\n", err.Error()) 208 | continue 209 | } 210 | 211 | var l layerN = &upsampleLayer{ 212 | scale: scale, 213 | } 214 | 215 | upsampleBlock, err := l.ToNode(g, input) 216 | if err != nil { 217 | fmt.Printf("\tError preparing Upsample block: %s\n", err.Error()) 218 | } 219 | networkNodes = append(networkNodes, upsampleBlock) 220 | input = upsampleBlock 221 | 222 | layers = append(layers, &l) 223 | 224 | filtersIdx = prevFilters 225 | break 226 | case "route": 227 | routeLayersStr, ok := block["layers"] 228 | if !ok { 229 | fmt.Printf("No field 'layers' for route layer") 230 | continue 231 | } 232 | layersSplit := strings.Split(routeLayersStr, ",") 233 | if len(layersSplit) < 1 { 234 | fmt.Printf("Something wrong with route layer. Check if it has one array item atleast") 235 | continue 236 | } 237 | for l := range layersSplit { 238 | layersSplit[l] = strings.TrimSpace(layersSplit[l]) 239 | } 240 | 241 | start := 0 242 | end := 0 243 | start, err := strconv.Atoi(layersSplit[0]) 244 | if err != nil { 245 | fmt.Printf("Each first element of 'layers' parameter for route layer should be an integer: %s\n", err.Error()) 246 | continue 247 | } 248 | if len(layersSplit) > 1 { 249 | end, err = strconv.Atoi(layersSplit[1]) 250 | if err != nil { 251 | fmt.Printf("Each second element of 'layers' parameter for route layer should be an integer: %s\n", err.Error()) 252 | continue 253 | } 254 | } 255 | 256 | if start > 0 { 257 | start = start - i 258 | } 259 | if end > 0 { 260 | end = end - i 261 | } 262 | 263 | l := routeLayer{ 264 | firstLayerIdx: i + start, 265 | secondLayerIdx: -1, 266 | } 267 | 268 | if end < 0 { 269 | l.secondLayerIdx = i + end 270 | filtersIdx = outputFilters[i+start] + outputFilters[i+end] 271 | } else { 272 | filtersIdx = outputFilters[i+start] 273 | } 274 | 275 | var ll layerN = &l 276 | 277 | routeBlock, err := l.ToNode(g, networkNodes...) 278 | if err != nil { 279 | fmt.Printf("\tError preparing Route block: %s\n", err.Error()) 280 | } 281 | networkNodes = append(networkNodes, routeBlock) 282 | input = routeBlock 283 | 284 | layers = append(layers, &ll) 285 | 286 | break 287 | case "yolo": 288 | maskStr, ok := block["mask"] 289 | if !ok { 290 | fmt.Printf("No field 'mask' for YOLO layer") 291 | continue 292 | } 293 | maskSplit := strings.Split(maskStr, ",") 294 | if len(maskSplit) < 1 { 295 | fmt.Printf("Something wrong with yolo layer. Check if it has one item in 'mask' array atleast") 296 | continue 297 | } 298 | masks := make([]int, len(maskSplit)) 299 | for l := range maskSplit { 300 | maskSplit[l] = strings.TrimSpace(maskSplit[l]) 301 | masks[l], err = strconv.Atoi(maskSplit[l]) 302 | if err != nil { 303 | fmt.Printf("Each element of 'mask' parameter for yolo layer should be an integer: %s\n", err.Error()) 304 | } 305 | } 306 | anchorsStr, ok := block["anchors"] 307 | if !ok { 308 | fmt.Printf("No field 'anchors' for YOLO layer") 309 | continue 310 | } 311 | anchorsSplit := strings.Split(anchorsStr, ",") 312 | if len(anchorsSplit) < 1 { 313 | fmt.Printf("Something wrong with yolo layer. Check if it has one item in 'anchors' array atleast") 314 | continue 315 | } 316 | if len(anchorsSplit)%2 != 0 { 317 | fmt.Printf("Number of elemnts in 'anchors' parameter for yolo layer should be divided exactly by 2 (even number)") 318 | continue 319 | } 320 | anchors := make([]int, len(anchorsSplit)) 321 | for l := range anchorsSplit { 322 | anchorsSplit[l] = strings.TrimSpace(anchorsSplit[l]) 323 | anchors[l], err = strconv.Atoi(anchorsSplit[l]) 324 | if err != nil { 325 | fmt.Printf("Each element of 'anchors' parameter for yolo layer should be an integer: %s\n", err.Error()) 326 | } 327 | } 328 | anchorsPairs := [][2]int{} 329 | for a := 0; a < len(anchors); a += 2 { 330 | anchorsPairs = append(anchorsPairs, [2]int{anchors[a], anchors[a+1]}) 331 | } 332 | selectedAnchors := [][2]int{} 333 | for m := range masks { 334 | selectedAnchors = append(selectedAnchors, anchorsPairs[masks[m]]) 335 | } 336 | flatten := []int{} 337 | for a := range selectedAnchors { 338 | flatten = append(flatten, selectedAnchors[a][0]) 339 | flatten = append(flatten, selectedAnchors[a][1]) 340 | } 341 | 342 | ignoreThreshStr, ok := block["ignore_thresh"] 343 | if !ok { 344 | fmt.Printf("Warning: no field 'ignore_thresh' for YOLO layer") 345 | } 346 | ignoreThresh64, err := strconv.ParseFloat(ignoreThreshStr, 32) 347 | if !ok { 348 | fmt.Printf("Warning: can't cast 'ignore_thresh' to float32 for YOLO layer") 349 | } 350 | yoloL := yoloLayer{ 351 | masks: masks, 352 | anchors: selectedAnchors, 353 | flattenAnchors: flatten, 354 | inputSize: shp[2], 355 | classesNum: classesNumber, 356 | ignoreThresh: float32(ignoreThresh64), 357 | } 358 | 359 | var l layerN = &yoloL 360 | 361 | yoloBlock, err := l.ToNode(g, input) 362 | if err != nil { 363 | fmt.Printf("\tError preparing YOLO block: %s\n", err.Error()) 364 | } 365 | networkNodes = append(networkNodes, yoloBlock) 366 | input = yoloBlock 367 | 368 | layers = append(layers, &l) 369 | yoloNodes = append(yoloNodes, yoloBlock) 370 | 371 | yoloTrainers = append(yoloTrainers, yoloL.yoloTrainer) 372 | 373 | filtersIdx = prevFilters 374 | break 375 | case "maxpool": 376 | sizeStr, ok := block["size"] 377 | if !ok { 378 | fmt.Printf("No field 'size' for maxpooling layer") 379 | continue 380 | } 381 | size, err := strconv.Atoi(sizeStr) 382 | if err != nil { 383 | fmt.Printf("'size' parameter for maxpooling layer should be an integer: %s\n", err.Error()) 384 | continue 385 | } 386 | strideStr, ok := block["stride"] 387 | if !ok { 388 | fmt.Printf("No field 'stride' for maxpooling layer") 389 | continue 390 | } 391 | stride, err := strconv.Atoi(strideStr) 392 | if err != nil { 393 | fmt.Printf("'size' parameter for maxpooling layer should be an integer: %s\n", err.Error()) 394 | continue 395 | } 396 | 397 | var l layerN = &maxPoolingLayer{ 398 | size: size, 399 | stride: stride, 400 | } 401 | maxpoolingBlock, err := l.ToNode(g, input) 402 | if err != nil { 403 | fmt.Printf("\tError preparing Max-Pooling block: %s\n", err.Error()) 404 | } 405 | networkNodes = append(networkNodes, maxpoolingBlock) 406 | input = maxpoolingBlock 407 | layers = append(layers, &l) 408 | filtersIdx = prevFilters 409 | break 410 | case "shortcut": 411 | fromStr, ok := block["from"] 412 | if !ok { 413 | fmt.Printf("No field 'from' for route layer") 414 | continue 415 | } 416 | from, err := strconv.Atoi(fromStr) 417 | if err != nil { 418 | fmt.Printf("Field 'from' should be of type int (value='%s')\n", fromStr) 419 | continue 420 | } 421 | 422 | l := shortcutLayer{ 423 | layerFromIdx: i + from, 424 | layerToIdx: i - 1, 425 | } 426 | 427 | var ll layerN = &l 428 | 429 | shortcutBlock, err := l.ToNode(g, networkNodes[i-1], networkNodes[i+from]) 430 | if err != nil { 431 | fmt.Printf("\tError preparing Shortcut block: %s\n", err.Error()) 432 | } 433 | networkNodes = append(networkNodes, shortcutBlock) 434 | input = shortcutBlock 435 | 436 | layers = append(layers, &ll) 437 | filtersIdx = prevFilters 438 | 439 | default: 440 | fmt.Printf("Impossible layer: '%s'\n", layerType) 441 | break 442 | } 443 | } 444 | prevFilters = filtersIdx 445 | outputFilters = append(outputFilters, filtersIdx) 446 | } 447 | 448 | // Pretty print 449 | linfo := []string{} 450 | for i := range layers { 451 | linfo = append(linfo, fmt.Sprintf("%v", *layers[i])) 452 | } 453 | 454 | model := &YOLOv3{ 455 | classesNum: classesNumber, 456 | boxesPerCell: boxesPerCell, 457 | netSize: netWidth, 458 | out: yoloNodes, 459 | layersInfo: linfo, 460 | LearningNodes: learningNodes, 461 | training: yoloTrainers, 462 | } 463 | 464 | return model, nil 465 | } 466 | 467 | // ActivateTrainingMode Activates training mode for unexported yoloOP 468 | func (net *YOLOv3) ActivateTrainingMode() error { 469 | if len(net.training) == 0 { 470 | return fmt.Errorf("Model doesn't contain any YOLO layer to activate training mode") 471 | } 472 | for i := range net.training { 473 | net.training[i].ActivateTrainingMode() 474 | } 475 | return nil 476 | } 477 | 478 | // DisableTrainingMode Disables training mode for unexported yoloOP 479 | func (net *YOLOv3) DisableTrainingMode() error { 480 | if len(net.training) == 0 { 481 | return fmt.Errorf("Model doesn't contain any YOLO layer to disable training mode") 482 | } 483 | for i := range net.training { 484 | net.training[i].DisableTrainingMode() 485 | } 486 | return nil 487 | } 488 | 489 | // SetTarget Set desired target for net's output (for training mode) 490 | func (net *YOLOv3) SetTarget(target []float32) error { 491 | if len(net.training) == 0 { 492 | return fmt.Errorf("Model has not any YOLO layers") 493 | } 494 | for i := range net.training { 495 | net.training[i].SetTarget(target) 496 | } 497 | return nil 498 | } 499 | --------------------------------------------------------------------------------