diff --git a/.DS_Store b/.DS_Store index 00ba490..2722bd9 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d5c0c18 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.png +*.jpg +*.jpeg +*.pth +*.npy diff --git a/GANimation/LICENSE b/GANimation/LICENSE deleted file mode 100644 index 94a9ed0..0000000 --- a/GANimation/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/GANimation/data/__init__.py b/GANimation/data/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/GANimation/data/custom_dataset_data_loader.py b/GANimation/data/custom_dataset_data_loader.py deleted file mode 100644 index c9774c2..0000000 --- a/GANimation/data/custom_dataset_data_loader.py +++ /dev/null @@ -1,25 +0,0 @@ -import torch.utils.data -from data.dataset import DatasetFactory - - -class CustomDatasetDataLoader: - def __init__(self, opt, is_for_train=True): - self._opt = opt - self._is_for_train = is_for_train - self._num_threds = opt.n_threads_train if is_for_train else opt.n_threads_test - self._create_dataset() - - def _create_dataset(self): - self._dataset = DatasetFactory.get_by_name(self._opt.dataset_mode, self._opt, self._is_for_train) - self._dataloader = torch.utils.data.DataLoader( - self._dataset, - batch_size=self._opt.batch_size, - shuffle=not self._opt.serial_batches, - num_workers=int(self._num_threds), - drop_last=True) - - def load_data(self): - return self._dataloader - - def __len__(self): - return len(self._dataset) diff --git a/GANimation/data/dataset.py b/GANimation/data/dataset.py deleted file mode 100644 index 9025fbd..0000000 --- a/GANimation/data/dataset.py +++ /dev/null @@ -1,68 +0,0 @@ -import torch.utils.data as data -from PIL import Image -import torchvision.transforms as transforms -import os -import os.path - - -class DatasetFactory: - def __init__(self): - pass - - @staticmethod - def get_by_name(dataset_name, opt, is_for_train): - if dataset_name == 'aus': - from data.dataset_aus import AusDataset - dataset = AusDataset(opt, is_for_train) - else: - raise ValueError("Dataset [%s] not recognized." % dataset_name) - - print('Dataset {} was created'.format(dataset.name)) - return dataset - - -class DatasetBase(data.Dataset): - def __init__(self, opt, is_for_train): - super(DatasetBase, self).__init__() - self._name = 'BaseDataset' - self._root = None - self._opt = opt - self._is_for_train = is_for_train - self._create_transform() - - self._IMG_EXTENSIONS = [ - '.jpg', '.JPG', '.jpeg', '.JPEG', - '.png', '.PNG', '.ppm', '.PPM', '.bmp', '.BMP', - ] - - @property - def name(self): - return self._name - - @property - def path(self): - return self._root - - def _create_transform(self): - self._transform = transforms.Compose([]) - - def get_transform(self): - return self._transform - - def _is_image_file(self, filename): - return any(filename.endswith(extension) for extension in self._IMG_EXTENSIONS) - - def _is_csv_file(self, filename): - return filename.endswith('.csv') - - def _get_all_files_in_subfolders(self, dir, is_file): - images = [] - assert os.path.isdir(dir), '%s is not a valid directory' % dir - - for root, _, fnames in sorted(os.walk(dir)): - for fname in fnames: - if is_file(fname): - path = os.path.join(root, fname) - images.append(path) - - return images diff --git a/GANimation/data/dataset_aus.py b/GANimation/data/dataset_aus.py deleted file mode 100644 index 2419274..0000000 --- a/GANimation/data/dataset_aus.py +++ /dev/null @@ -1,117 +0,0 @@ -import os.path -import torchvision.transforms as transforms -from data.dataset import DatasetBase -from PIL import Image -import random -import numpy as np -import pickle -from utils import cv_utils - - -class AusDataset(DatasetBase): - def __init__(self, opt, is_for_train): - super(AusDataset, self).__init__(opt, is_for_train) - self._name = 'AusDataset' - - # read dataset - self._read_dataset_paths() - - def __getitem__(self, index): - assert (index < self._dataset_size) - - # start_time = time.time() - real_img = None - real_cond = None - while real_img is None or real_cond is None: - # if sample randomly: overwrite index - if not self._opt.serial_batches: - index = random.randint(0, self._dataset_size - 1) - - # get sample data - sample_id = self._ids[index] - - real_img, real_img_path = self._get_img_by_id(sample_id) - real_cond = self._get_cond_by_id(sample_id) - - if real_img is None: - print 'error reading image %s, skipping sample' % sample_id - if real_cond is None: - print 'error reading aus %s, skipping sample' % sample_id - - desired_cond = self._generate_random_cond() - - # transform data - img = self._transform(Image.fromarray(real_img)) - - # pack data - sample = {'real_img': img, - 'real_cond': real_cond, - 'desired_cond': desired_cond, - 'sample_id': sample_id, - 'real_img_path': real_img_path - } - - # print (time.time() - start_time) - - return sample - - def __len__(self): - return self._dataset_size - - def _read_dataset_paths(self): - self._root = self._opt.data_dir - self._imgs_dir = os.path.join(self._root, self._opt.images_folder) - - # read ids - use_ids_filename = self._opt.train_ids_file if self._is_for_train else self._opt.test_ids_file - use_ids_filepath = os.path.join(self._root, use_ids_filename) - self._ids = self._read_ids(use_ids_filepath) - - # read aus - conds_filepath = os.path.join(self._root, self._opt.aus_file) - self._conds = self._read_conds(conds_filepath) - - self._ids = list(set(self._ids).intersection(set(self._conds.keys()))) - - # dataset size - self._dataset_size = len(self._ids) - - def _create_transform(self): - if self._is_for_train: - transform_list = [transforms.RandomHorizontalFlip(), - transforms.ToTensor(), - transforms.Normalize(mean=[0.5, 0.5, 0.5], - std=[0.5, 0.5, 0.5]), - ] - else: - transform_list = [transforms.ToTensor(), - transforms.Normalize(mean=[0.5, 0.5, 0.5], - std=[0.5, 0.5, 0.5]), - ] - self._transform = transforms.Compose(transform_list) - - def _read_ids(self, file_path): - ids = np.loadtxt(file_path, delimiter='\t', dtype=np.str) - return [id[:-4] for id in ids] - - def _read_conds(self, file_path): - with open(file_path, 'rb') as f: - return pickle.load(f) - - def _get_cond_by_id(self, id): - if id in self._conds: - return self._conds[id]/5.0 - else: - return None - - def _get_img_by_id(self, id): - filepath = os.path.join(self._imgs_dir, id+'.jpg') - return cv_utils.read_cv2_img(filepath), filepath - - def _generate_random_cond(self): - cond = None - while cond is None: - rand_sample_id = self._ids[random.randint(0, self._dataset_size - 1)] - cond = self._get_cond_by_id(rand_sample_id) - cond += np.random.uniform(-0.1, 0.1, cond.shape) - return cond diff --git a/GANimation/data/prepare_au_annotations.py b/GANimation/data/prepare_au_annotations.py deleted file mode 100644 index aab9a08..0000000 --- a/GANimation/data/prepare_au_annotations.py +++ /dev/null @@ -1,39 +0,0 @@ -import numpy as np -import os -from tqdm import tqdm -import argparse -import glob -import re -import pickle - -parser = argparse.ArgumentParser() -parser.add_argument('-ia', '--input_aus_filesdir', type=str, help='Dir with imgs aus files') -parser.add_argument('-op', '--output_path', type=str, help='Output path') -args = parser.parse_args() - -def get_data(filepaths): - data = dict() - for filepath in tqdm(filepaths): - content = np.loadtxt(filepath, delimiter=', ', skiprows=1) - data[os.path.basename(filepath[:-4])] = content[2:19] - - return data - -def save_dict(data, name): - with open(name + '.pkl', 'wb') as f: - pickle.dump(data, f, pickle.HIGHEST_PROTOCOL) - -def main(): - filepaths = glob.glob(os.path.join(args.input_aus_filesdir, '*.csv')) - filepaths.sort() - - # create aus file - data = get_data(filepaths) - - if not os.path.isdir(args.output_path): - os.makedirs(args.output_path) - save_dict(data, os.path.join(args.output_path, "aus")) - - -if __name__ == '__main__': - main() diff --git a/GANimation/launch/run_train.sh b/GANimation/launch/run_train.sh deleted file mode 100644 index b427f40..0000000 --- a/GANimation/launch/run_train.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -python train.py \ ---data_dir path/to/dataset/ \ ---name experiment_1 \ ---batch_size 25 \ diff --git a/GANimation/networks/__init__.py b/GANimation/networks/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/GANimation/networks/discriminator_wasserstein_gan.py b/GANimation/networks/discriminator_wasserstein_gan.py deleted file mode 100644 index 7c10084..0000000 --- a/GANimation/networks/discriminator_wasserstein_gan.py +++ /dev/null @@ -1,30 +0,0 @@ -import torch.nn as nn -import numpy as np -from .networks import NetworkBase - -class Discriminator(NetworkBase): - """Discriminator. PatchGAN.""" - def __init__(self, image_size=128, conv_dim=64, c_dim=5, repeat_num=6): - super(Discriminator, self).__init__() - self._name = 'discriminator_wgan' - - layers = [] - layers.append(nn.Conv2d(3, conv_dim, kernel_size=4, stride=2, padding=1)) - layers.append(nn.LeakyReLU(0.01, inplace=True)) - - curr_dim = conv_dim - for i in range(1, repeat_num): - layers.append(nn.Conv2d(curr_dim, curr_dim*2, kernel_size=4, stride=2, padding=1)) - layers.append(nn.LeakyReLU(0.01, inplace=True)) - curr_dim = curr_dim * 2 - - k_size = int(image_size / np.power(2, repeat_num)) - self.main = nn.Sequential(*layers) - self.conv1 = nn.Conv2d(curr_dim, 1, kernel_size=3, stride=1, padding=1, bias=False) - self.conv2 = nn.Conv2d(curr_dim, c_dim, kernel_size=k_size, bias=False) - - def forward(self, x): - h = self.main(x) - out_real = self.conv1(h) - out_aux = self.conv2(h) - return out_real.squeeze(), out_aux.squeeze() \ No newline at end of file diff --git a/GANimation/networks/generator_wasserstein_gan.py b/GANimation/networks/generator_wasserstein_gan.py deleted file mode 100644 index b0d95f9..0000000 --- a/GANimation/networks/generator_wasserstein_gan.py +++ /dev/null @@ -1,68 +0,0 @@ -import torch.nn as nn -import numpy as np -from .networks import NetworkBase -import torch - -class Generator(NetworkBase): - """Generator. Encoder-Decoder Architecture.""" - def __init__(self, conv_dim=64, c_dim=5, repeat_num=6): - super(Generator, self).__init__() - self._name = 'generator_wgan' - - layers = [] - layers.append(nn.Conv2d(3+c_dim, conv_dim, kernel_size=7, stride=1, padding=3, bias=False)) - layers.append(nn.InstanceNorm2d(conv_dim, affine=True)) - layers.append(nn.ReLU(inplace=True)) - - # Down-Sampling - curr_dim = conv_dim - for i in range(2): - layers.append(nn.Conv2d(curr_dim, curr_dim*2, kernel_size=4, stride=2, padding=1, bias=False)) - layers.append(nn.InstanceNorm2d(curr_dim*2, affine=True)) - layers.append(nn.ReLU(inplace=True)) - curr_dim = curr_dim * 2 - - # Bottleneck - for i in range(repeat_num): - layers.append(ResidualBlock(dim_in=curr_dim, dim_out=curr_dim)) - - # Up-Sampling - for i in range(2): - layers.append(nn.ConvTranspose2d(curr_dim, curr_dim//2, kernel_size=4, stride=2, padding=1, bias=False)) - layers.append(nn.InstanceNorm2d(curr_dim//2, affine=True)) - layers.append(nn.ReLU(inplace=True)) - curr_dim = curr_dim // 2 - - self.main = nn.Sequential(*layers) - - layers = [] - layers.append(nn.Conv2d(curr_dim, 3, kernel_size=7, stride=1, padding=3, bias=False)) - layers.append(nn.Tanh()) - self.img_reg = nn.Sequential(*layers) - - layers = [] - layers.append(nn.Conv2d(curr_dim, 1, kernel_size=7, stride=1, padding=3, bias=False)) - layers.append(nn.Sigmoid()) - self.attetion_reg = nn.Sequential(*layers) - - def forward(self, x, c): - # replicate spatially and concatenate domain information - c = c.unsqueeze(2).unsqueeze(3) - c = c.expand(c.size(0), c.size(1), x.size(2), x.size(3)) - x = torch.cat([x, c], dim=1) - features = self.main(x) - return self.img_reg(features), self.attetion_reg(features) - -class ResidualBlock(nn.Module): - """Residual Block.""" - def __init__(self, dim_in, dim_out): - super(ResidualBlock, self).__init__() - self.main = nn.Sequential( - nn.Conv2d(dim_in, dim_out, kernel_size=3, stride=1, padding=1, bias=False), - nn.InstanceNorm2d(dim_out, affine=True), - nn.ReLU(inplace=True), - nn.Conv2d(dim_out, dim_out, kernel_size=3, stride=1, padding=1, bias=False), - nn.InstanceNorm2d(dim_out, affine=True)) - - def forward(self, x): - return x + self.main(x) \ No newline at end of file diff --git a/GANimation/networks/networks.py b/GANimation/networks/networks.py deleted file mode 100644 index c2fb9ed..0000000 --- a/GANimation/networks/networks.py +++ /dev/null @@ -1,57 +0,0 @@ -import torch.nn as nn -import functools - -class NetworksFactory: - def __init__(self): - pass - - @staticmethod - def get_by_name(network_name, *args, **kwargs): - - if network_name == 'generator_wasserstein_gan': - from .generator_wasserstein_gan import Generator - network = Generator(*args, **kwargs) - elif network_name == 'discriminator_wasserstein_gan': - from .discriminator_wasserstein_gan import Discriminator - network = Discriminator(*args, **kwargs) - else: - raise ValueError("Network %s not recognized." % network_name) - - print "Network %s was created" % network_name - - return network - - -class NetworkBase(nn.Module): - def __init__(self): - super(NetworkBase, self).__init__() - self._name = 'BaseNetwork' - - @property - def name(self): - return self._name - - def init_weights(self): - self.apply(self._weights_init_fn) - - def _weights_init_fn(self, m): - classname = m.__class__.__name__ - if classname.find('Conv') != -1: - m.weight.data.normal_(0.0, 0.02) - if hasattr(m.bias, 'data'): - m.bias.data.fill_(0) - elif classname.find('BatchNorm2d') != -1: - m.weight.data.normal_(1.0, 0.02) - m.bias.data.fill_(0) - - def _get_norm_layer(self, norm_type='batch'): - if norm_type == 'batch': - norm_layer = functools.partial(nn.BatchNorm2d, affine=True) - elif norm_type == 'instance': - norm_layer = functools.partial(nn.InstanceNorm2d, affine=False) - elif norm_type =='batchnorm2d': - norm_layer = nn.BatchNorm2d - else: - raise NotImplementedError('normalization layer [%s] is not found' % norm_type) - - return norm_layer diff --git a/GANimation/options/__init__.py b/GANimation/options/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/GANimation/options/base_options.py b/GANimation/options/base_options.py deleted file mode 100644 index a785e35..0000000 --- a/GANimation/options/base_options.py +++ /dev/null @@ -1,108 +0,0 @@ -import argparse -import os -from utils import util -import torch - -class BaseOptions(): - def __init__(self): - self._parser = argparse.ArgumentParser() - self._initialized = False - - def initialize(self): - self._parser.add_argument('--data_dir', type=str, help='path to dataset') - self._parser.add_argument('--train_ids_file', type=str, default='train_ids.csv', help='file containing train ids') - self._parser.add_argument('--test_ids_file', type=str, default='test_ids.csv', help='file containing test ids') - self._parser.add_argument('--images_folder', type=str, default='imgs', help='images folder') - self._parser.add_argument('--aus_file', type=str, default='aus_openface.pkl', help='file containing samples aus') - - self._parser.add_argument('--load_epoch', type=int, default=-1, help='which epoch to load? set to -1 to use latest cached model') - self._parser.add_argument('--batch_size', type=int, default=4, help='input batch size') - self._parser.add_argument('--image_size', type=int, default=128, help='input image size') - self._parser.add_argument('--cond_nc', type=int, default=17, help='# of conditions') - self._parser.add_argument('--gpu_ids', type=str, default='0', help='gpu ids: e.g. 0 0,1,2, 0,2. use -1 for CPU') - self._parser.add_argument('--name', type=str, default='experiment_1', help='name of the experiment. It decides where to store samples and models') - self._parser.add_argument('--dataset_mode', type=str, default='aus', help='chooses dataset to be used') - self._parser.add_argument('--model', type=str, default='ganimation', help='model to run[au_net_model]') - self._parser.add_argument('--n_threads_test', default=1, type=int, help='# threads for loading data') - self._parser.add_argument('--checkpoints_dir', type=str, default='./checkpoints', help='models are saved here') - self._parser.add_argument('--serial_batches', action='store_true', help='if true, takes images in order to make batches, otherwise takes them randomly') - self._parser.add_argument('--do_saturate_mask', action="store_true", default=False, help='do use mask_fake for mask_cyc') - - - - - self._initialized = True - - def parse(self): - if not self._initialized: - self.initialize() - self._opt = self._parser.parse_args() - - # set is train or set - self._opt.is_train = self.is_train - - # set and check load_epoch - self._set_and_check_load_epoch() - - # get and set gpus - self._get_set_gpus() - - args = vars(self._opt) - - # print in terminal args - self._print(args) - - # save args to file - self._save(args) - - return self._opt - - def _set_and_check_load_epoch(self): - models_dir = os.path.join(self._opt.checkpoints_dir, self._opt.name) - if os.path.exists(models_dir): - if self._opt.load_epoch == -1: - load_epoch = 0 - for file in os.listdir(models_dir): - if file.startswith("net_epoch_"): - load_epoch = max(load_epoch, int(file.split('_')[2])) - self._opt.load_epoch = load_epoch - else: - found = False - for file in os.listdir(models_dir): - if file.startswith("net_epoch_"): - found = int(file.split('_')[2]) == self._opt.load_epoch - if found: break - assert found, 'Model for epoch %i not found' % self._opt.load_epoch - else: - assert self._opt.load_epoch < 1, 'Model for epoch %i not found' % self._opt.load_epoch - self._opt.load_epoch = 0 - - def _get_set_gpus(self): - # get gpu ids - str_ids = self._opt.gpu_ids.split(',') - self._opt.gpu_ids = [] - for str_id in str_ids: - id = int(str_id) - if id >= 0: - self._opt.gpu_ids.append(id) - - # set gpu ids - if len(self._opt.gpu_ids) > 0: - torch.cuda.set_device(self._opt.gpu_ids[0]) - - def _print(self, args): - print('------------ Options -------------') - for k, v in sorted(args.items()): - print('%s: %s' % (str(k), str(v))) - print('-------------- End ----------------') - - def _save(self, args): - expr_dir = os.path.join(self._opt.checkpoints_dir, self._opt.name) - print(expr_dir) - util.mkdirs(expr_dir) - file_name = os.path.join(expr_dir, 'opt_%s.txt' % ('train' if self.is_train else 'test')) - with open(file_name, 'wt') as opt_file: - opt_file.write('------------ Options -------------\n') - for k, v in sorted(args.items()): - opt_file.write('%s: %s\n' % (str(k), str(v))) - opt_file.write('-------------- End ----------------\n') diff --git a/GANimation/options/test_options.py b/GANimation/options/test_options.py deleted file mode 100644 index 259bfc1..0000000 --- a/GANimation/options/test_options.py +++ /dev/null @@ -1,9 +0,0 @@ -from .base_options import BaseOptions - - -class TestOptions(BaseOptions): - def initialize(self): - BaseOptions.initialize(self) - self._parser.add_argument('--input_path', type=str, help='path to image') - self._parser.add_argument('--output_dir', type=str, default='./output', help='output path') - self.is_train = False diff --git a/GANimation/options/train_options.py b/GANimation/options/train_options.py deleted file mode 100644 index c3c923c..0000000 --- a/GANimation/options/train_options.py +++ /dev/null @@ -1,31 +0,0 @@ -from .base_options import BaseOptions - - -class TrainOptions(BaseOptions): - def initialize(self): - BaseOptions.initialize(self) - self._parser.add_argument('--n_threads_train', default=4, type=int, help='# threads for loading data') - self._parser.add_argument('--num_iters_validate', default=1, type=int, help='# batches to use when validating') - self._parser.add_argument('--print_freq_s', type=int, default=60, help='frequency of showing training results on console') - self._parser.add_argument('--display_freq_s', type=int, default=300, help='frequency [s] of showing training results on screen') - self._parser.add_argument('--save_latest_freq_s', type=int, default=3600, help='frequency of saving the latest results') - - self._parser.add_argument('--nepochs_no_decay', type=int, default=20, help='# of epochs at starting learning rate') - self._parser.add_argument('--nepochs_decay', type=int, default=10, help='# of epochs to linearly decay learning rate to zero') - - self._parser.add_argument('--train_G_every_n_iterations', type=int, default=5, help='train G every n interations') - self._parser.add_argument('--poses_g_sigma', type=float, default=0.06, help='initial learning rate for adam') - self._parser.add_argument('--lr_G', type=float, default=0.0001, help='initial learning rate for G adam') - self._parser.add_argument('--G_adam_b1', type=float, default=0.5, help='beta1 for G adam') - self._parser.add_argument('--G_adam_b2', type=float, default=0.999, help='beta2 for G adam') - self._parser.add_argument('--lr_D', type=float, default=0.0001, help='initial learning rate for D adam') - self._parser.add_argument('--D_adam_b1', type=float, default=0.5, help='beta1 for D adam') - self._parser.add_argument('--D_adam_b2', type=float, default=0.999, help='beta2 for D adam') - self._parser.add_argument('--lambda_D_prob', type=float, default=1, help='lambda for real/fake discriminator loss') - self._parser.add_argument('--lambda_D_cond', type=float, default=4000, help='lambda for condition discriminator loss') - self._parser.add_argument('--lambda_cyc', type=float, default=10, help='lambda cycle loss') - self._parser.add_argument('--lambda_mask', type=float, default=0.1, help='lambda mask loss') - self._parser.add_argument('--lambda_D_gp', type=float, default=10, help='lambda gradient penalty loss') - self._parser.add_argument('--lambda_mask_smooth', type=float, default=1e-5, help='lambda mask smooth loss') - - self.is_train = True diff --git a/GANimation/requirements.txt b/GANimation/requirements.txt deleted file mode 100644 index b40caf1..0000000 --- a/GANimation/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -numpy -matplotlib -tqdm -dlib -face_recognition -opencv-contrib-python diff --git a/GANimation/sample_dataset/aus_openface.pkl b/GANimation/sample_dataset/aus_openface.pkl deleted file mode 100644 index a3d340a..0000000 Binary files a/GANimation/sample_dataset/aus_openface.pkl and /dev/null differ diff --git a/GANimation/sample_dataset/imgs/N_0000000356_00190.jpg b/GANimation/sample_dataset/imgs/N_0000000356_00190.jpg deleted file mode 100644 index 7c2ef5c..0000000 Binary files a/GANimation/sample_dataset/imgs/N_0000000356_00190.jpg and /dev/null differ diff --git a/GANimation/sample_dataset/imgs/N_0000000437_00540.jpg b/GANimation/sample_dataset/imgs/N_0000000437_00540.jpg deleted file mode 100644 index 52cdcf1..0000000 Binary files a/GANimation/sample_dataset/imgs/N_0000000437_00540.jpg and /dev/null differ diff --git a/GANimation/sample_dataset/imgs/N_0000001507_00202.jpg b/GANimation/sample_dataset/imgs/N_0000001507_00202.jpg deleted file mode 100644 index a93e268..0000000 Binary files a/GANimation/sample_dataset/imgs/N_0000001507_00202.jpg and /dev/null differ diff --git a/GANimation/sample_dataset/imgs/N_0000001939_00054.jpg b/GANimation/sample_dataset/imgs/N_0000001939_00054.jpg deleted file mode 100644 index 9049bd6..0000000 Binary files a/GANimation/sample_dataset/imgs/N_0000001939_00054.jpg and /dev/null differ diff --git a/GANimation/sample_dataset/test_ids.csv b/GANimation/sample_dataset/test_ids.csv deleted file mode 100644 index 6590ab2..0000000 --- a/GANimation/sample_dataset/test_ids.csv +++ /dev/null @@ -1,2 +0,0 @@ -N_0000001507_00202.jpg -N_0000001939_00054.jpg diff --git a/GANimation/sample_dataset/train_ids.csv b/GANimation/sample_dataset/train_ids.csv deleted file mode 100644 index b47b7c3..0000000 --- a/GANimation/sample_dataset/train_ids.csv +++ /dev/null @@ -1,2 +0,0 @@ -N_0000000437_00540.jpg -N_0000000356_00190.jpg diff --git a/GANimation/test.py b/GANimation/test.py deleted file mode 100644 index 9ec0c9b..0000000 --- a/GANimation/test.py +++ /dev/null @@ -1,74 +0,0 @@ -import os -import argparse -import glob -import cv2 -from utils import face_utils -from utils import cv_utils -import face_recognition -from PIL import Image -import torchvision.transforms as transforms -import torch -import pickle -import numpy as np -from models.models import ModelsFactory -from options.test_options import TestOptions - -class MorphFacesInTheWild: - def __init__(self, opt): - self._opt = opt - self._model = ModelsFactory.get_by_name(self._opt.model, self._opt) - self._model.set_eval() - self._transform = transforms.Compose([transforms.ToTensor(), - transforms.Normalize(mean=[0.5, 0.5, 0.5], - std=[0.5, 0.5, 0.5]) - ]) - - def morph_file(self, img_path, expresion): - img = cv_utils.read_cv2_img(img_path) - morphed_img = self._img_morph(img, expresion) - output_name = '%s_out.png' % os.path.basename(img_path) - self._save_img(morphed_img, output_name) - - def _img_morph(self, img, expresion): - bbs = face_recognition.face_locations(img) - if len(bbs) > 0: - y, right, bottom, x = bbs[0] - bb = x, y, (right - x), (bottom - y) - face = face_utils.crop_face_with_bb(img, bb) - face = face_utils.resize_face(face) - else: - face = face_utils.resize_face(img) - - morphed_face = self._morph_face(face, expresion) - - return morphed_face - - def _morph_face(self, face, expresion): - face = torch.unsqueeze(self._transform(Image.fromarray(face)), 0) - expresion = torch.unsqueeze(torch.from_numpy(expresion/5.0), 0) - test_batch = {'real_img': face, 'real_cond': expresion, 'desired_cond': expresion, 'sample_id': torch.FloatTensor(), 'real_img_path': []} - self._model.set_input(test_batch) - imgs, _ = self._model.forward(keep_data_for_visuals=False, return_estimates=True) - return imgs['concat'] - - def _save_img(self, img, filename): - filepath = os.path.join(self._opt.output_dir, filename) - img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) - cv2.imwrite(filepath, img) - - -def main(): - opt = TestOptions().parse() - if not os.path.isdir(opt.output_dir): - os.makedirs(opt.output_dir) - - morph = MorphFacesInTheWild(opt) - - image_path = opt.input_path - expression = np.random.uniform(0, 1, opt.cond_nc) - morph.morph_file(image_path, expression) - - - -if __name__ == '__main__': - main() diff --git a/GANimation/train.py b/GANimation/train.py deleted file mode 100644 index 2a4602f..0000000 --- a/GANimation/train.py +++ /dev/null @@ -1,141 +0,0 @@ -import time -from options.train_options import TrainOptions -from data.custom_dataset_data_loader import CustomDatasetDataLoader -from models.models import ModelsFactory -from utils.tb_visualizer import TBVisualizer -from collections import OrderedDict -import os - - -class Train: - def __init__(self): - self._opt = TrainOptions().parse() - data_loader_train = CustomDatasetDataLoader(self._opt, is_for_train=True) - data_loader_test = CustomDatasetDataLoader(self._opt, is_for_train=False) - - self._dataset_train = data_loader_train.load_data() - self._dataset_test = data_loader_test.load_data() - - self._dataset_train_size = len(data_loader_train) - self._dataset_test_size = len(data_loader_test) - print('#train images = %d' % self._dataset_train_size) - print('#test images = %d' % self._dataset_test_size) - - self._model = ModelsFactory.get_by_name(self._opt.model, self._opt) - self._tb_visualizer = TBVisualizer(self._opt) - - self._train() - - def _train(self): - self._total_steps = self._opt.load_epoch * self._dataset_train_size - self._iters_per_epoch = self._dataset_train_size / self._opt.batch_size - self._last_display_time = None - self._last_save_latest_time = None - self._last_print_time = time.time() - - for i_epoch in range(self._opt.load_epoch + 1, self._opt.nepochs_no_decay + self._opt.nepochs_decay + 1): - epoch_start_time = time.time() - - # train epoch - self._train_epoch(i_epoch) - - # save model - print('saving the model at the end of epoch %d, iters %d' % (i_epoch, self._total_steps)) - self._model.save(i_epoch) - - # print epoch info - time_epoch = time.time() - epoch_start_time - print('End of epoch %d / %d \t Time Taken: %d sec (%d min or %d h)' % - (i_epoch, self._opt.nepochs_no_decay + self._opt.nepochs_decay, time_epoch, - time_epoch / 60, time_epoch / 3600)) - - # update learning rate - if i_epoch > self._opt.nepochs_no_decay: - self._model.update_learning_rate() - - def _train_epoch(self, i_epoch): - epoch_iter = 0 - self._model.set_train() - for i_train_batch, train_batch in enumerate(self._dataset_train): - iter_start_time = time.time() - - # display flags - do_visuals = self._last_display_time is None or time.time() - self._last_display_time > self._opt.display_freq_s - do_print_terminal = time.time() - self._last_print_time > self._opt.print_freq_s or do_visuals - - # train model - self._model.set_input(train_batch) - train_generator = ((i_train_batch+1) % self._opt.train_G_every_n_iterations == 0) or do_visuals - self._model.optimize_parameters(keep_data_for_visuals=do_visuals, train_generator=train_generator) - - # update epoch info - self._total_steps += self._opt.batch_size - epoch_iter += self._opt.batch_size - - # display terminal - if do_print_terminal: - self._display_terminal(iter_start_time, i_epoch, i_train_batch, do_visuals) - self._last_print_time = time.time() - - # display visualizer - if do_visuals: - self._display_visualizer_train(self._total_steps) - self._display_visualizer_val(i_epoch, self._total_steps) - self._last_display_time = time.time() - - # save model - if self._last_save_latest_time is None or time.time() - self._last_save_latest_time > self._opt.save_latest_freq_s: - print('saving the latest model (epoch %d, total_steps %d)' % (i_epoch, self._total_steps)) - self._model.save(i_epoch) - self._last_save_latest_time = time.time() - - def _display_terminal(self, iter_start_time, i_epoch, i_train_batch, visuals_flag): - errors = self._model.get_current_errors() - t = (time.time() - iter_start_time) / self._opt.batch_size - self._tb_visualizer.print_current_train_errors(i_epoch, i_train_batch, self._iters_per_epoch, errors, t, visuals_flag) - - def _display_visualizer_train(self, total_steps): - self._tb_visualizer.display_current_results(self._model.get_current_visuals(), total_steps, is_train=True) - self._tb_visualizer.plot_scalars(self._model.get_current_errors(), total_steps, is_train=True) - self._tb_visualizer.plot_scalars(self._model.get_current_scalars(), total_steps, is_train=True) - - def _display_visualizer_val(self, i_epoch, total_steps): - val_start_time = time.time() - - # set model to eval - self._model.set_eval() - - # evaluate self._opt.num_iters_validate epochs - val_errors = OrderedDict() - for i_val_batch, val_batch in enumerate(self._dataset_test): - if i_val_batch == self._opt.num_iters_validate: - break - - # evaluate model - self._model.set_input(val_batch) - self._model.forward(keep_data_for_visuals=(i_val_batch == 0)) - errors = self._model.get_current_errors() - - # store current batch errors - for k, v in errors.iteritems(): - if k in val_errors: - val_errors[k] += v - else: - val_errors[k] = v - - # normalize errors - for k in val_errors.iterkeys(): - val_errors[k] /= self._opt.num_iters_validate - - # visualize - t = (time.time() - val_start_time) - self._tb_visualizer.print_current_validate_errors(i_epoch, val_errors, t) - self._tb_visualizer.plot_scalars(val_errors, total_steps, is_train=False) - self._tb_visualizer.display_current_results(self._model.get_current_visuals(), total_steps, is_train=False) - - # set model back to train - self._model.set_train() - - -if __name__ == "__main__": - Train() diff --git a/GANimation/utils/__init__.py b/GANimation/utils/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/GANimation/utils/cv_utils.py b/GANimation/utils/cv_utils.py deleted file mode 100644 index f974352..0000000 --- a/GANimation/utils/cv_utils.py +++ /dev/null @@ -1,54 +0,0 @@ -import cv2 -from matplotlib import pyplot as plt -import numpy as np - -def read_cv2_img(path): - ''' - Read color images - :param path: Path to image - :return: Only returns color images - ''' - img = cv2.imread(path, -1) - - if img is not None: - if len(img.shape) != 3: - return None - - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - - return img - -def show_cv2_img(img, title='img'): - ''' - Display cv2 image - :param img: cv::mat - :param title: title - :return: None - ''' - plt.imshow(img) - plt.title(title) - plt.axis('off') - plt.show() - -def show_images_row(imgs, titles, rows=1): - ''' - Display grid of cv2 images image - :param img: list [cv::mat] - :param title: titles - :return: None - ''' - assert ((titles is None) or (len(imgs) == len(titles))) - num_images = len(imgs) - - if titles is None: - titles = ['Image (%d)' % i for i in range(1, num_images + 1)] - - fig = plt.figure() - for n, (image, title) in enumerate(zip(imgs, titles)): - ax = fig.add_subplot(rows, np.ceil(num_images / float(rows)), n + 1) - if image.ndim == 2: - plt.gray() - plt.imshow(image) - ax.set_title(title) - plt.axis('off') - plt.show() \ No newline at end of file diff --git a/GANimation/utils/face_utils.py b/GANimation/utils/face_utils.py deleted file mode 100644 index ce17e35..0000000 --- a/GANimation/utils/face_utils.py +++ /dev/null @@ -1,71 +0,0 @@ -import face_recognition -import cv2 -import numpy as np -import skimage -import skimage.transform -import warnings - -def detect_faces(img): - ''' - Detect faces in image - :param img: cv::mat HxWx3 RGB - :return: yield 4 - ''' - # detect faces - bbs = face_recognition.face_locations(img) - - for y, right, bottom, x in bbs: - # Scale back up face bb - yield x, y, (right - x), (bottom - y) - -def detect_biggest_face(img): - ''' - Detect biggest face in image - :param img: cv::mat HxWx3 RGB - :return: 4 - ''' - # detect faces - bbs = face_recognition.face_locations(img) - - max_area = float('-inf') - max_area_i = 0 - for i, (y, right, bottom, x) in enumerate(bbs): - area = (right - x) * (bottom - y) - if max_area < area: - max_area = area - max_area_i = i - - if max_area != float('-inf'): - y, right, bottom, x = bbs[max_area_i] - return x, y, (right - x), (bottom - y) - - return None - -def crop_face_with_bb(img, bb): - ''' - Crop face in image given bb - :param img: cv::mat HxWx3 - :param bb: 4 () - :return: HxWx3 - ''' - x, y, w, h = bb - return img[y:y+h, x:x+w, :] - -def place_face(img, face, bb): - x, y, w, h = bb - face = resize_face(face, size=(w, h)) - img[y:y+h, x:x+w] = face - return img - -def resize_face(face_img, size=(128, 128)): - ''' - Resize face to a given size - :param face_img: cv::mat HxWx3 - :param size: new H and W (size x size). 128 by default. - :return: cv::mat size x size x 3 - ''' - return cv2.resize(face_img, size) - -def detect_landmarks(face_img): - landmakrs = face_recognition.face_landmarks(face_img) - return landmakrs[0] if len(landmakrs) > 0 else None diff --git a/GANimation/utils/plots.py b/GANimation/utils/plots.py deleted file mode 100644 index 99bd1fa..0000000 --- a/GANimation/utils/plots.py +++ /dev/null @@ -1,67 +0,0 @@ -from __future__ import print_function -import numpy as np -import matplotlib.pyplot as plt - -def plot_au(img, aus, title=None): - ''' - Plot action units - :param img: HxWx3 - :param aus: N - :return: - ''' - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) - ax.axis('off') - fig.subplots_adjust(0, 0, 0.8, 1) # get rid of margins - - # display img - ax.imshow(img) - - if len(aus) == 11: - au_ids = ['1','2','4','5','6','9','12','17','20','25','26'] - x = 0.1 - y = 0.39 - i = 0 - for au, id in zip(aus, au_ids): - if id == '9': - x = 0.5 - y -= .15 - i = 0 - elif id == '12': - x = 0.1 - y -= .15 - i = 0 - - ax.text(x + i * 0.2, y, id, horizontalalignment='center', verticalalignment='center', - transform=ax.transAxes, color='r', fontsize=20) - ax.text((x-0.001)+i*0.2, y-0.07, au, horizontalalignment='center', verticalalignment='center', - transform=ax.transAxes, color='b', fontsize=20) - i+=1 - - else: - au_ids = ['1', '2', '4', '5', '6', '7', '9', '10', '12', '14', '15', '17', '20', '23', '25', '26', '45'] - x = 0.1 - y = 0.39 - i = 0 - for au, id in zip(aus, au_ids): - if id == '9' or id == '20': - x = 0.1 - y -= .15 - i = 0 - - ax.text(x + i * 0.2, y, id, horizontalalignment='center', verticalalignment='center', - transform=ax.transAxes, color='r', fontsize=20) - ax.text((x-0.001)+i*0.2, y-0.07, au, horizontalalignment='center', verticalalignment='center', - transform=ax.transAxes, color='b', fontsize=20) - i+=1 - - if title is not None: - ax.text(0.5, 0.95, title, horizontalalignment='center', verticalalignment='center', - transform=ax.transAxes, color='r', fontsize=20) - - fig.canvas.draw() - data = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep='') - data = data.reshape(fig.canvas.get_width_height()[::-1] + (3,)) - plt.close(fig) - - return data \ No newline at end of file diff --git a/GANimation/utils/tb_visualizer.py b/GANimation/utils/tb_visualizer.py deleted file mode 100644 index 6e758b9..0000000 --- a/GANimation/utils/tb_visualizer.py +++ /dev/null @@ -1,66 +0,0 @@ -import numpy as np -import os -import time -from . import util -from tensorboardX import SummaryWriter - - -class TBVisualizer: - def __init__(self, opt): - self._opt = opt - self._save_path = os.path.join(opt.checkpoints_dir, opt.name) - - self._log_path = os.path.join(self._save_path, 'loss_log2.txt') - self._tb_path = os.path.join(self._save_path, 'summary.json') - self._writer = SummaryWriter(self._save_path) - - with open(self._log_path, "a") as log_file: - now = time.strftime("%c") - log_file.write('================ Training Loss (%s) ================\n' % now) - - def __del__(self): - self._writer.close() - - def display_current_results(self, visuals, it, is_train, save_visuals=False): - for label, image_numpy in visuals.items(): - sum_name = '{}/{}'.format('Train' if is_train else 'Test', label) - self._writer.add_image(sum_name, image_numpy, it) - - if save_visuals: - util.save_image(image_numpy, - os.path.join(self._opt.checkpoints_dir, self._opt.name, - 'event_imgs', sum_name, '%08d.png' % it)) - - self._writer.export_scalars_to_json(self._tb_path) - - def plot_scalars(self, scalars, it, is_train): - for label, scalar in scalars.items(): - sum_name = '{}/{}'.format('Train' if is_train else 'Test', label) - self._writer.add_scalar(sum_name, scalar, it) - - def print_current_train_errors(self, epoch, i, iters_per_epoch, errors, t, visuals_were_stored): - log_time = time.strftime("[%d/%m/%Y %H:%M:%S]") - visuals_info = "v" if visuals_were_stored else "" - message = '%s (T%s, epoch: %d, it: %d/%d, t/smpl: %.3fs) ' % (log_time, visuals_info, epoch, i, iters_per_epoch, t) - for k, v in errors.items(): - message += '%s:%.3f ' % (k, v) - - print(message) - with open(self._log_path, "a") as log_file: - log_file.write('%s\n' % message) - - def print_current_validate_errors(self, epoch, errors, t): - log_time = time.strftime("[%d/%m/%Y %H:%M:%S]") - message = '%s (V, epoch: %d, time_to_val: %ds) ' % (log_time, epoch, t) - for k, v in errors.items(): - message += '%s:%.3f ' % (k, v) - - print(message) - with open(self._log_path, "a") as log_file: - log_file.write('%s\n' % message) - - def save_images(self, visuals): - for label, image_numpy in visuals.items(): - image_name = '%s.png' % label - save_path = os.path.join(self._save_path, "samples", image_name) - util.save_image(image_numpy, save_path) \ No newline at end of file diff --git a/GANimation/utils/util.py b/GANimation/utils/util.py deleted file mode 100644 index 5a3a0ae..0000000 --- a/GANimation/utils/util.py +++ /dev/null @@ -1,53 +0,0 @@ -from __future__ import print_function -from PIL import Image -import numpy as np -import os -import torchvision -import math - - -def tensor2im(img, imtype=np.uint8, unnormalize=True, idx=0, nrows=None): - # select a sample or create grid if img is a batch - if len(img.shape) == 4: - nrows = nrows if nrows is not None else int(math.sqrt(img.size(0))) - img = img[idx] if idx >= 0 else torchvision.utils.make_grid(img, nrows) - - img = img.cpu().float() - if unnormalize: - mean = [0.5, 0.5, 0.5] - std = [0.5, 0.5, 0.5] - - for i, m, s in zip(img, mean, std): - i.mul_(s).add_(m) - - image_numpy = img.numpy() - image_numpy_t = np.transpose(image_numpy, (1, 2, 0)) - image_numpy_t = image_numpy_t*254.0 - - return image_numpy_t.astype(imtype) - -def tensor2maskim(mask, imtype=np.uint8, idx=0, nrows=1): - im = tensor2im(mask, imtype=imtype, idx=idx, unnormalize=False, nrows=nrows) - if im.shape[2] == 1: - im = np.repeat(im, 3, axis=-1) - return im - -def mkdirs(paths): - if isinstance(paths, list) and not isinstance(paths, str): - for path in paths: - mkdir(path) - else: - mkdir(paths) - -def mkdir(path): - if not os.path.exists(path): - os.makedirs(path) - -def save_image(image_numpy, image_path): - mkdir(os.path.dirname(image_path)) - image_pil = Image.fromarray(image_numpy) - image_pil.save(image_path) - -def save_str_data(data, path): - mkdir(os.path.dirname(path)) - np.savetxt(path, data, delimiter=",", fmt="%s") \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..30e5aea --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# Disrupting Deepfakes: Adversarial Attacks on Conditional Image Translation Networks + +## StarGAN Testing +``` +python main.py --mode test --dataset CelebA --image_size 256 --c_dim 5 --selected_attrs Black_Hair Blond_Hair Brown_Hair Male Young --model_save_dir='stargan_celeba_256/models' --result_dir='stargan_celeba_256/results_test' --test_iters 200000 --batch_size 1 +``` + +## StarGAN Training +``` +python main.py --mode train --dataset CelebA --image_size 256 --c_dim 5 --sample_dir stargan_both/samples --log_dir stargan_both/logs --model_save_dir stargan_both/models --result_dir stargan_both/results --selected_attrs Black_Hair Blond_Hair Brown_Hair Male Young +``` + +## GANimation +``` +python main.py --mode animation +``` + +## pix2pixHD +``` +python test.py --name label2city_1024p --netG local --ngf 32 --resize_or_crop none +``` + +## CycleGAN +``` +python test.py --dataroot datasets/horse2zebra/testA --name horse2zebra_pretrained --model test --no_dropout +``` \ No newline at end of file diff --git a/cyclegan/checkpoints b/cyclegan/checkpoints new file mode 120000 index 0000000..a750d1a --- /dev/null +++ b/cyclegan/checkpoints @@ -0,0 +1 @@ +/home/grad3/nruiz9/research/fsynth/cyclegan/checkpoints \ No newline at end of file diff --git a/cyclegan/datasets/horse2zebra b/cyclegan/datasets/horse2zebra new file mode 120000 index 0000000..122eb6d --- /dev/null +++ b/cyclegan/datasets/horse2zebra @@ -0,0 +1 @@ +/scratch2/fsynth/cyclegan/datasets/horse2zebra \ No newline at end of file diff --git a/cyclegan/datasets/monet2photo b/cyclegan/datasets/monet2photo new file mode 120000 index 0000000..4e87f15 --- /dev/null +++ b/cyclegan/datasets/monet2photo @@ -0,0 +1 @@ +/scratch2/fsynth/cyclegan/datasets/monet2photo \ No newline at end of file diff --git a/cyclegan/models/test_model.py b/cyclegan/models/test_model.py index 4c3be38..aec91da 100644 --- a/cyclegan/models/test_model.py +++ b/cyclegan/models/test_model.py @@ -71,13 +71,12 @@ class TestModel(BaseModel): """Run forward pass.""" self.fake_noattack = self.netG(self.real) # G(real) - def attack(self): + def attack(self, target): image = self.real + # Attack pgd_attack = attacks.LinfPGDAttack(model=self.netG) - black = np.zeros((1, 3, image.size(2), image.size(3))) - black = torch.FloatTensor(black).cuda() - input_adv, perturb = pgd_attack.perturb(image, black) + input_adv, perturb = pgd_attack.perturb(image, target) return input_adv, perturb @@ -92,8 +91,13 @@ class TestModel(BaseModel): l2 = F.mse_loss(generated, generated_noattack) l0 = (generated - generated_noattack).norm(0) d = (generated - generated_noattack).norm(float('-inf')) + + if F.mse_loss(generated, generated_noattack) > 0.05: + n_dist = 1 + else: + n_dist = 0 - return l1, l2, l0, d + return l1, l2, l0, d, n_dist def optimize_parameters(self): """No optimization for test model.""" diff --git a/cyclegan/test.py b/cyclegan/test.py index 8ab5082..a3591ea 100644 --- a/cyclegan/test.py +++ b/cyclegan/test.py @@ -61,29 +61,35 @@ if __name__ == '__main__': torch.manual_seed(0) # Initialize Metrics - l1_error, l2_error, min_dist, l0_error, perceptual_error = 0.0, 0.0, 0.0, 0.0, 0.0 - n_samples = 0 + l1_error, l2_error, min_dist, l0_error = 0.0, 0.0, 0.0, 0.0 + n_dist, n_samples = 0, 0 for i, data in enumerate(dataset): if i >= opt.num_test: # only apply our model to opt.num_test images. break model.set_input(data) # unpack data from data loader + + # Get ground-truth output with torch.no_grad(): model.forward_noattack() - if i == 0: - input_adv, perturb = model.attack() - # input_adv, perturb = model.attack() + + # Attack + input_adv, perturb = model.attack(target=model.fake_noattack) + + # Get output from adversarial sample with torch.no_grad(): model.forward_attack(perturb) model.compute_visuals() # Compute metrics - l1, l2, l0, d = model.compute_errors() + l1, l2, l0, d, above = model.compute_errors() l1_error += l1 l2_error += l2 l0_error += l0 min_dist += d + n_dist += above n_samples += 1 + # model.test() # run inference visuals = model.get_current_visuals() # get image results img_path = model.get_image_paths() # get image paths @@ -92,8 +98,8 @@ if __name__ == '__main__': save_images(webpage, visuals, img_path, aspect_ratio=opt.aspect_ratio, width=opt.display_winsize) # Print metrics - print('{} images. L1 error: {}. L2 error: {}. L0 error: {}. L_-inf error: {}. Perceptual error: {}.'.format(n_samples, - l1_error / n_samples, l2_error / n_samples, l0_error / n_samples, min_dist / n_samples, perceptual_error / n_samples)) + print('{} images. L1 error: {}. L2 error: {}. prop_dist: {}. L0 error: {}. L_-inf error: {}.'.format(n_samples, + l1_error / n_samples, l2_error / n_samples, float(n_dist) / n_samples, l0_error / n_samples, min_dist / n_samples)) webpage.save() # save the HTML diff --git a/cyclegan/util/attacks.py b/cyclegan/util/attacks.py index a9a654b..8b9a296 100644 --- a/cyclegan/util/attacks.py +++ b/cyclegan/util/attacks.py @@ -7,26 +7,39 @@ import torch import torch.nn as nn class LinfPGDAttack(object): - def __init__(self, model=None, epsilon=0.05, k=1, a=0.05): + def __init__(self, model=None, epsilon=0.05, k=10, a=0.01): + """ + FGSM, I-FGSM and PGD attacks + epsilon: magnitude of attack + k: iterations + a: step size + """ self.model = model self.epsilon = epsilon self.k = k self.a = a self.loss_fn = nn.MSELoss() + # PGD or I-FGSM? + self.rand = True + def perturb(self, X_nat, y): """ - Given examples (X_nat, y), returns adversarial - examples within epsilon of X_nat in l_infinity norm. + Vanilla Attack. """ - X = X_nat.clone().detach_() + if self.rand: + X = X_nat.clone().detach_() + torch.tensor(np.random.uniform(-self.epsilon, self.epsilon, X_nat.shape).astype('float32')).cuda() + else: + X = X_nat.clone().detach_() + # use the following if FGSM or I-FGSM and random seeds are fixed + # X = X_nat.clone().detach_() + torch.tensor(np.random.uniform(-0.001, 0.001, X_nat.shape).astype('float32')).cuda() for i in range(self.k): - print('test', i) X.requires_grad = True output = self.model(X) self.model.zero_grad() + # Minus in the loss means "towards" and plus means "away from" loss = self.loss_fn(output, y) loss.backward() grad = X.grad diff --git a/ganimation/attacks.py b/ganimation/attacks.py index 5916639..dcbc497 100644 --- a/ganimation/attacks.py +++ b/ganimation/attacks.py @@ -7,7 +7,13 @@ import torch import torch.nn as nn class LinfPGDAttack(object): - def __init__(self, model=None, device=None, epsilon=0.03, k=80, a=0.01): + def __init__(self, model=None, device=None, epsilon=0.05, k=10, a=0.01): + """ + FGSM, I-FGSM and PGD attacks + epsilon: magnitude of attack + k: iterations + a: step size + """ self.model = model self.epsilon = epsilon self.k = k @@ -15,23 +21,34 @@ class LinfPGDAttack(object): self.loss_fn = nn.MSELoss().to(device) self.device = device + # PGD or I-FGSM? + self.rand = True + def perturb(self, X_nat, y, c_trg): """ - Given examples (X_nat, y), returns adversarial - examples within epsilon of X_nat in l_infinity norm. + Vanilla Attack. """ - X = X_nat.clone().detach_() + if self.rand: + X = X_nat.clone().detach_() + torch.tensor(np.random.uniform(-self.epsilon, self.epsilon, X_nat.shape).astype('float32')).to(self.device) + else: + X = X_nat.clone().detach_() + # use the following if FGSM or I-FGSM and random seeds are fixed + # X = X_nat.clone().detach_() + torch.tensor(np.random.uniform(-0.001, 0.001, X_nat.shape).astype('float32')).cuda() for i in range(self.k): - # print(i) X.requires_grad = True output_att, output_img = self.model(X, c_trg) out = imFromAttReg(output_att, output_img, X) self.model.zero_grad() - loss = self.loss_fn(output_att, y) - # loss = -self.loss_fn(out, y) + + # Attention attack + # loss = self.loss_fn(output_att, y) + + # Output attack + # Minus in the loss means "towards" and plus means "away from" + loss = self.loss_fn(out, y) loss.backward() grad = X.grad @@ -40,41 +57,8 @@ class LinfPGDAttack(object): eta = torch.clamp(X_adv - X_nat, min=-self.epsilon, max=self.epsilon) X = torch.clamp(X_nat + eta, min=-1, max=1).detach_() - return X, eta - - def perturb_iter_data(self, X_nat, X_all, y, c_trg): - """ - X_nat is a tensor with several different images. - This does not work at all yet.. - """ - X = X_nat.clone().detach_() - # X_all_local = X_all.clone().detach_() - - j = 0 - J = X_all.size(0) - J = 1 - - for i in range(self.k): - # print(i,j) - X_j = X_all[j].unsqueeze(0) - X_j.requires_grad = True - output_att, output_img = self.model(X_j, c_trg) - - out = imFromAttReg(output_att, output_img, X_j) - - self.model.zero_grad() - loss = -self.loss_fn(out, y) - loss.backward() - grad = X_j.grad - - X_adv = X + self.a * grad.sign() - - eta = torch.clamp(X_adv - X_nat, min=-self.epsilon, max=self.epsilon) - X = torch.clamp(X_nat + eta, min=-1, max=1).detach_() - - j += 1 - if j == J: - j = 0 + # Debug + # X_adv, loss, grad, output_att, output_img = None, None, None, None, None return X, eta @@ -88,7 +72,6 @@ class LinfPGDAttack(object): J = c_trg.size(0) for i in range(self.k): - # print(i) X.requires_grad = True output_att, output_img = self.model(X, c_trg[j,:].unsqueeze(0)) @@ -96,8 +79,8 @@ class LinfPGDAttack(object): self.model.zero_grad() - loss = self.loss_fn(output_att, y) - # loss = -self.loss_fn(out, y) + # loss = self.loss_fn(output_att, y) + loss = self.loss_fn(out, y) loss.backward() grad = X.grad @@ -126,13 +109,12 @@ class LinfPGDAttack(object): self.model.zero_grad() for j in range(J): - # print(i, j) output_att, output_img = self.model(X, c_trg[j,:].unsqueeze(0)) out = imFromAttReg(output_att, output_img, X) - loss = self.loss_fn(output_att, y) - # loss = -self.loss_fn(out, y) + # loss = self.loss_fn(output_att, y) + loss = self.loss_fn(out, y) full_loss += loss full_loss.backward() diff --git a/ganimation/config.py b/ganimation/config.py index a98eb27..de3b363 100644 --- a/ganimation/config.py +++ b/ganimation/config.py @@ -75,7 +75,7 @@ def get_config(): # parser.add_argument('--animation_images_dir', type=str, # default='animations/eric_andre/images_to_animate') parser.add_argument('--animation_images_dir', type=str, - default='data/celeba_small/') + default='data/celeba/images_aligned/new_small') parser.add_argument('--animation_attribute_images_dir', type=str, default='animations/eric_andre/attribute_images') parser.add_argument('--animation_attributes_path', type=str, diff --git a/ganimation/models b/ganimation/models index 7027e3b..bbcf753 120000 --- a/ganimation/models +++ b/ganimation/models @@ -1 +1 @@ -/scratch2/ganimation/models \ No newline at end of file +/home/grad3/nruiz9/research/fsynth/ganimation/models \ No newline at end of file diff --git a/ganimation/solver.py b/ganimation/solver.py index 8dc9e43..2d14638 100644 --- a/ganimation/solver.py +++ b/ganimation/solver.py @@ -384,66 +384,51 @@ class Solver(Utils): reference_expression_images[target_idx])) if mode == 'animate_image': - - black = np.zeros((1,3,128,128)) - black = torch.FloatTensor(black).to(self.device) - # Initialize Metrics - l1_error = 0.0 - l2_error = 0.0 - min_dist = 0.0 - l0_error = 0.0 - perceptual_error = 0.0 - n_samples = 0 + l1_error, l2_error, min_dist, l0_error = 0.0, 0.0, 0.0, 0.0 + n_dist, n_samples = 0, 0 pgd_attack = attacks.LinfPGDAttack(model=self.G, device=self.device) images_to_animate_path = sorted(glob.glob( self.animation_images_dir + '/*')) - x_advs = [] - for idx, image_path in enumerate(images_to_animate_path): image_to_animate = regular_image_transform(Image.open(image_path)).unsqueeze(0).cuda() - all_images = torch.cat([regular_image_transform(Image.open(path)).unsqueeze(0) for path in images_to_animate_path], dim=0).cuda() + for target_idx in range(targets.size(0)-1): + print('image', idx, 'AU', target_idx) - # Transfer to different images - # if idx == 0: - # for target_idx in range(targets.size(0)): - # x_adv, perturb = pgd_attack.perturb(image_to_animate, black, targets[target_idx, :].unsqueeze(0).cuda()) - # x_advs.append((x_adv, perturb)) - - for target_idx in range(targets.size(0)): - # Transfer to different classes - # if target_idx == 0: - # img = regular_image_transform(Image.open(images_to_animate_path[idx])).unsqueeze(0).cuda() - - # Wrong Class - # x_adv, perturb = pgd_attack.perturb(image_to_animate, black, targets[0, :].unsqueeze(0).cuda()) - - # Joint Class Conditional - # x_adv, perturb = pgd_attack.perturb_joint_class(image_to_animate, black, targets[:, :].cuda()) - - # Iterative Class Conditional - # x_adv, perturb = pgd_attack.perturb_iter_class(image_to_animate, black, targets[:, :].cuda()) - - # Iterative Data - # _, perturb = pgd_attack.perturb_iter_data(image_to_animate, all_images, black, targets[68, :].unsqueeze(0).cuda()) - targets_au = targets[target_idx, :].unsqueeze(0).cuda() + with torch.no_grad(): + resulting_images_att_noattack, resulting_images_reg_noattack = self.G( + image_to_animate, targets_au) + resulting_image_noattack = self.imFromAttReg( + resulting_images_att_noattack, resulting_images_reg_noattack, image_to_animate).cuda() + + # Transfer to different classes + # if target_idx == 0: + # Wrong Class + # x_adv, perturb = pgd_attack.perturb(image_to_animate, image_to_animate, targets[0, :].unsqueeze(0).cuda()) + + # Joint Class Conditional + # x_adv, perturb = pgd_attack.perturb_joint_class(image_to_animate, image_to_animate, targets[:, :].cuda()) + + # Iterative Class Conditional + # x_adv, perturb = pgd_attack.perturb_iter_class(image_to_animate, image_to_animate, targets[:, :].cuda()) + + # Iterative Data + # _, perturb = pgd_attack.perturb_iter_data(image_to_animate, all_images, image_to_animate, targets[68, :].unsqueeze(0).cuda()) + # Normal Attack - # x_adv, perturb = pgd_attack.perturb(image_to_animate, black, targets_au) + x_adv, perturb = pgd_attack.perturb(image_to_animate, resulting_image_noattack, targets_au) - # x_adv, perturb = x_advs[target_idx] - - # x_adv = image_to_animate + perturb + # Use this line if transferring attacks + x_adv = image_to_animate + perturb # No Attack - x_adv = image_to_animate - - # print(image_to_animate.shape, x_adv.shape) + # x_adv = image_to_animate with torch.no_grad(): resulting_images_att, resulting_images_reg = self.G( @@ -451,12 +436,6 @@ class Solver(Utils): resulting_image = self.imFromAttReg( resulting_images_att, resulting_images_reg, x_adv).cuda() - # with torch.no_grad(): - # resulting_images_att_noattack, resulting_images_reg_noattack = self.G( - # image_to_animate, targets_au) - # resulting_image_noattack = self.imFromAttReg( - # resulting_images_att_noattack, resulting_images_reg_noattack, image_to_animate).cuda() - save_image((resulting_image+1)/2, os.path.join(self.animation_results_dir, image_path.split('/')[-1].split('.')[0] + '_' + reference_expression_images[target_idx])) @@ -465,50 +444,27 @@ class Solver(Utils): image_path.split('/')[-1].split('.')[0] + '_ref.jpg')) - # l1_error += F.l1_loss(resulting_image, resulting_image_noattack) - # l2_error += F.mse_loss(resulting_image, resulting_image_noattack) - # l0_error += (resulting_image - resulting_image_noattack).norm(0) - # min_dist += (resulting_image - resulting_image_noattack).norm(float('-inf')) + # Compare to ground-truth output + l1_error += F.l1_loss(resulting_image, resulting_image_noattack) + l2_error += F.mse_loss(resulting_image, resulting_image_noattack) + l0_error += (resulting_image - resulting_image_noattack).norm(0) + min_dist += (resulting_image - resulting_image_noattack).norm(float('-inf')) # Compare to input image - l1_error += F.l1_loss(resulting_image, image_to_animate) - l2_error += F.mse_loss(resulting_image, image_to_animate) - l0_error += (resulting_image - image_to_animate).norm(0) - min_dist += (resulting_image - image_to_animate).norm(float('-inf')) + # l1_error += F.l1_loss(resulting_image, x_adv) + # l2_error += F.mse_loss(resulting_image, x_adv) + # l0_error += (resulting_image - x_adv).norm(0) + # min_dist += (resulting_image - x_adv).norm(float('-inf')) + if F.mse_loss(resulting_image, resulting_image_noattack) > 0.05: + n_dist += 1 n_samples += 1 + # Debug + # x_adv, targets_au, resulting_image, resulting_images_att, resulting_images_reg = None, None, None, None, None + + image_to_animate = None + # Print metrics - print('{} images. L1 error: {}. L2 error: {}. L0 error: {}. L_-inf error: {}. Perceptual error: {}.'.format(n_samples, - l1_error / n_samples, l2_error / n_samples, l0_error / n_samples, min_dist / n_samples, perceptual_error / n_samples)) - - # """ Code to modify single Action Units """ - - # Set data loader. - # self.data_loader = self.data_loader - - # with torch.no_grad(): - # for i, (self.x_real, c_org) in enumerate(self.data_loader): - - # # Prepare input images and target domain labels. - # self.x_real = self.x_real.to(self.device) - # c_org = c_org.to(self.device) - - # # c_trg_list = self.create_labels(self.data_loader) - - # crit, cl_regression = self.D(self.x_real) - # # print(crit) - # print("ORIGINAL", c_org[0]) - # print("REGRESSION", cl_regression[0]) - - # for au in range(17): - # alpha = np.linspace(-0.3,0.3,10) - # for j, a in enumerate(alpha): - # new_emotion = c_org.clone() - # new_emotion[:,au]=torch.clamp(new_emotion[:,au]+a, 0, 1) - # attention, reg = self.G(self.x_real, new_emotion) - # x_fake = self.imFromAttReg(attention, reg, self.x_real) - # save_image((x_fake+1)/2, os.path.join(self.result_dir, '{}-{}-{}-images.jpg'.format(i,au,j))) - - # if i >= 3: - # break + print('{} images. L1 error: {}. L2 error: {}. prop_dist: {}. L0 error: {}. L_-inf error: {}.'.format(n_samples, + l1_error / n_samples, l2_error / n_samples, float(n_dist) / float(n_samples), l0_error / n_samples, min_dist / n_samples)) diff --git a/ganimation/video_results/eric_andre.gif b/ganimation/video_results/eric_andre.gif deleted file mode 100644 index 5bd8ee1..0000000 Binary files a/ganimation/video_results/eric_andre.gif and /dev/null differ diff --git a/ganimation/video_results/frida.gif b/ganimation/video_results/frida.gif deleted file mode 100644 index f84b54c..0000000 Binary files a/ganimation/video_results/frida.gif and /dev/null differ diff --git a/ganimation/video_results/standard_celeba.avi b/ganimation/video_results/standard_celeba.avi deleted file mode 100644 index 6639cfe..0000000 Binary files a/ganimation/video_results/standard_celeba.avi and /dev/null differ diff --git a/ganimation/video_results/standard_emotionet_.avi b/ganimation/video_results/standard_emotionet_.avi deleted file mode 100644 index 5bb78d3..0000000 Binary files a/ganimation/video_results/standard_emotionet_.avi and /dev/null differ diff --git a/ganimation/video_results/virtual-celeba.avi b/ganimation/video_results/virtual-celeba.avi deleted file mode 100644 index ab41397..0000000 Binary files a/ganimation/video_results/virtual-celeba.avi and /dev/null differ diff --git a/ganimation/video_results/virtual_emotionet.avi b/ganimation/video_results/virtual_emotionet.avi deleted file mode 100644 index f8bff33..0000000 Binary files a/ganimation/video_results/virtual_emotionet.avi and /dev/null differ diff --git a/pix2pixHD/.ipynb_checkpoints/avspeech_dataload-checkpoint.ipynb b/pix2pixHD/.ipynb_checkpoints/avspeech_dataload-checkpoint.ipynb deleted file mode 100644 index 4fcecfd..0000000 --- a/pix2pixHD/.ipynb_checkpoints/avspeech_dataload-checkpoint.ipynb +++ /dev/null @@ -1,206 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 370, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "import numpy as np\n", - "from matplotlib import pyplot as plt\n", - "from PIL import Image\n", - "import cv2\n", - "import sys\n", - "import data.landmarks as landmarks" - ] - }, - { - "cell_type": "code", - "execution_count": 371, - "metadata": {}, - "outputs": [], - "source": [ - "# Example\n", - "ref_frame = 40\n", - "tgt_frame = 50\n", - "ref_img = Image.open('datasets/avspeech/frames/iUBL2Vowiulk/{}.png'.format(ref_frame))\n", - "tgt_img = Image.open('datasets/avspeech/frames/iUBL2Vowiulk/{}.png'.format(tgt_frame))\n", - "meta = dict(np.load('datasets/avspeech/meta/iUBL2Vowiulk.npz'))" - ] - }, - { - "cell_type": "code", - "execution_count": 372, - "metadata": {}, - "outputs": [], - "source": [ - "def get_relative_landmarks(meta, frame_num):\n", - " centerx, centery, l = meta['bbox'][frame_num - 1]\n", - " orig_height = meta['length'].item()\n", - " orig_width = meta['width'].item()\n", - " landmarks = meta['landmarks_2d'][frame_num - 1]\n", - " new_landmarks = landmarks.copy()\n", - " \n", - " # Go from frame landmarks to cropped and resized frame landmarks\n", - " x_left = max(0, centerx-l)\n", - " x_right = min(centerx+l, orig_height)\n", - " y_up = max(0, centery-l)\n", - " y_down = min(centery+l, orig_width)\n", - " w = x_right - x_left\n", - " h = y_down - y_up\n", - " ar_h = 255. / h\n", - " ar_w = 255. / w\n", - "\n", - " new_landmarks[:,0] -= (centery - l)\n", - " new_landmarks[:,1] -= (centerx - l)\n", - " new_landmarks[:,0] *= ar_h\n", - " new_landmarks[:,1] *= ar_w\n", - " \n", - " return new_landmarks" - ] - }, - { - "cell_type": "code", - "execution_count": 395, - "metadata": {}, - "outputs": [], - "source": [ - "def plot_landmarks(frame, landmarks):\n", - " fig = plt.figure(figsize=(256, 256), dpi=1)\n", - " ax = fig.add_subplot(111)\n", - " ax.axis('off')\n", - "# plt.imshow(frame)\n", - " plt.imshow(np.ones((256, 256, 3)))\n", - " plt.subplots_adjust(left=0, right=1, top=1, bottom=0)\n", - " \n", - " lw = 100\n", - "\n", - " # Head\n", - " ax.plot(landmarks[0:17, 0], landmarks[0:17, 1], linestyle='-', color='green', lw=lw)\n", - " # Eyebrows\n", - " ax.plot(landmarks[17:22, 0], landmarks[17:22, 1], linestyle='-', color='orange', lw=lw)\n", - " ax.plot(landmarks[22:27, 0], landmarks[22:27, 1], linestyle='-', color='orange', lw=lw)\n", - " # Nose\n", - " ax.plot(landmarks[27:31, 0], landmarks[27:31, 1], linestyle='-', color='blue', lw=lw)\n", - " ax.plot(landmarks[31:36, 0], landmarks[31:36, 1], linestyle='-', color='blue', lw=lw)\n", - " # Eyes\n", - " ax.plot(landmarks[36:42, 0], landmarks[36:42, 1], linestyle='-', color='red', lw=lw)\n", - " ax.plot(landmarks[42:48, 0], landmarks[42:48, 1], linestyle='-', color='red', lw=lw)\n", - " ax.plot([landmarks[36, 0], landmarks[41, 0]], [landmarks[36, 1], landmarks[41, 1]], \n", - " linestyle='-', color='red', lw=lw)\n", - " ax.plot([landmarks[42, 0], landmarks[47, 0]], [landmarks[42, 1], landmarks[47, 1]], \n", - " linestyle='-', color='red', lw=lw)\n", - " # Mouth\n", - " ax.plot(landmarks[48:60, 0], landmarks[48:60, 1], linestyle='-', color='purple', lw=lw)\n", - " ax.plot([landmarks[48, 0], landmarks[59, 0]], [landmarks[48, 1], landmarks[59, 1]], \n", - " linestyle='-', color='purple', lw=lw)\n", - "\n", - " fig.canvas.draw()\n", - " data = Image.frombuffer('RGB', fig.canvas.get_width_height(), fig.canvas.tostring_rgb(), 'raw', 'RGB', 0, 1)\n", - "# data = data.rotate(180)\n", - " plt.close(fig)\n", - " return data" - ] - }, - { - "cell_type": "code", - "execution_count": 396, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 396, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "plt.imshow(plot_landmarks(ref_img, get_relative_landmarks(meta, ref_frame)))" - ] - }, - { - "cell_type": "code", - "execution_count": 346, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 346, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "plt.imshow(ref_img)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.4" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/pix2pixHD/avspeech_dataload.ipynb b/pix2pixHD/avspeech_dataload.ipynb deleted file mode 100644 index f8a2540..0000000 --- a/pix2pixHD/avspeech_dataload.ipynb +++ /dev/null @@ -1,204 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 370, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "import numpy as np\n", - "from matplotlib import pyplot as plt\n", - "from PIL import Image\n", - "import cv2\n", - "import sys\n", - "import data.landmarks as landmarks" - ] - }, - { - "cell_type": "code", - "execution_count": 371, - "metadata": {}, - "outputs": [], - "source": [ - "# Example\n", - "ref_frame = 40\n", - "tgt_frame = 50\n", - "ref_img = Image.open('datasets/avspeech/frames/iUBL2Vowiulk/{}.png'.format(ref_frame))\n", - "tgt_img = Image.open('datasets/avspeech/frames/iUBL2Vowiulk/{}.png'.format(tgt_frame))\n", - "meta = dict(np.load('datasets/avspeech/meta/iUBL2Vowiulk.npz'))" - ] - }, - { - "cell_type": "code", - "execution_count": 372, - "metadata": {}, - "outputs": [], - "source": [ - "def get_relative_landmarks(meta, frame_num):\n", - " centerx, centery, l = meta['bbox'][frame_num - 1]\n", - " orig_height = meta['length'].item()\n", - " orig_width = meta['width'].item()\n", - " landmarks = meta['landmarks_2d'][frame_num - 1]\n", - " new_landmarks = landmarks.copy()\n", - " \n", - " # Go from frame landmarks to cropped and resized frame landmarks\n", - " x_left = max(0, centerx-l)\n", - " x_right = min(centerx+l, orig_height)\n", - " y_up = max(0, centery-l)\n", - " y_down = min(centery+l, orig_width)\n", - " w = x_right - x_left\n", - " h = y_down - y_up\n", - " ar_h = 255. / h\n", - " ar_w = 255. / w\n", - "\n", - " new_landmarks[:,0] -= (centery - l)\n", - " new_landmarks[:,1] -= (centerx - l)\n", - " new_landmarks[:,0] *= ar_h\n", - " new_landmarks[:,1] *= ar_w\n", - " \n", - " return new_landmarks" - ] - }, - { - "cell_type": "code", - "execution_count": 397, - "metadata": {}, - "outputs": [], - "source": [ - "def plot_landmarks(landmarks):\n", - " fig = plt.figure(figsize=(256, 256), dpi=1)\n", - " ax = fig.add_subplot(111)\n", - " ax.axis('off')\n", - " plt.imshow(np.ones((256, 256, 3)))\n", - " plt.subplots_adjust(left=0, right=1, top=1, bottom=0)\n", - " \n", - " lw = 100\n", - "\n", - " # Head\n", - " ax.plot(landmarks[0:17, 0], landmarks[0:17, 1], linestyle='-', color='green', lw=lw)\n", - " # Eyebrows\n", - " ax.plot(landmarks[17:22, 0], landmarks[17:22, 1], linestyle='-', color='orange', lw=lw)\n", - " ax.plot(landmarks[22:27, 0], landmarks[22:27, 1], linestyle='-', color='orange', lw=lw)\n", - " # Nose\n", - " ax.plot(landmarks[27:31, 0], landmarks[27:31, 1], linestyle='-', color='blue', lw=lw)\n", - " ax.plot(landmarks[31:36, 0], landmarks[31:36, 1], linestyle='-', color='blue', lw=lw)\n", - " # Eyes\n", - " ax.plot(landmarks[36:42, 0], landmarks[36:42, 1], linestyle='-', color='red', lw=lw)\n", - " ax.plot(landmarks[42:48, 0], landmarks[42:48, 1], linestyle='-', color='red', lw=lw)\n", - " ax.plot([landmarks[36, 0], landmarks[41, 0]], [landmarks[36, 1], landmarks[41, 1]], \n", - " linestyle='-', color='red', lw=lw)\n", - " ax.plot([landmarks[42, 0], landmarks[47, 0]], [landmarks[42, 1], landmarks[47, 1]], \n", - " linestyle='-', color='red', lw=lw)\n", - " # Mouth\n", - " ax.plot(landmarks[48:60, 0], landmarks[48:60, 1], linestyle='-', color='purple', lw=lw)\n", - " ax.plot([landmarks[48, 0], landmarks[59, 0]], [landmarks[48, 1], landmarks[59, 1]], \n", - " linestyle='-', color='purple', lw=lw)\n", - "\n", - " fig.canvas.draw()\n", - " data = Image.frombuffer('RGB', fig.canvas.get_width_height(), fig.canvas.tostring_rgb(), 'raw', 'RGB', 0, 1)\n", - " plt.close(fig)\n", - " return data" - ] - }, - { - "cell_type": "code", - "execution_count": 398, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 398, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "plt.imshow(plot_landmarks(get_relative_landmarks(meta, ref_frame)))" - ] - }, - { - "cell_type": "code", - "execution_count": 399, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 399, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "plt.imshow(ref_img)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.4" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/pix2pixHD/checkpoints b/pix2pixHD/checkpoints index 5c90d1e..9c5fa1c 120000 --- a/pix2pixHD/checkpoints +++ b/pix2pixHD/checkpoints @@ -1 +1 @@ -/scratch2/fsynth/checkpoints \ No newline at end of file +/home/grad3/nruiz9/research/fsynth/pix2pixHD_attack/checkpoints \ No newline at end of file diff --git a/pix2pixHD/data/avspeech.py b/pix2pixHD/data/avspeech.py deleted file mode 100644 index b4bd7ef..0000000 --- a/pix2pixHD/data/avspeech.py +++ /dev/null @@ -1,51 +0,0 @@ -import os -import numpy as np -from matplotlib import pyplot as plt -from PIL import Image -from data import landmarks -from torchvision import transforms -from torch.utils.data.dataset import Dataset -import glob -import random - -class AVSpeech(Dataset): - def __init__(self, transform): - self.frame_folder = 'datasets/avspeech/frames' - self.meta_folder = 'datasets/avspeech/meta' - self.user_folders = glob.glob(os.path.join(self.frame_folder, '*')) - self.users = [x.split('/')[-1] for x in self.user_folders] - - self.transform = transform - - self.length = len(self.users) - - def __getitem__(self, index): - # Get list of frames for user - user = self.users[index] - meta = dict(np.load(os.path.join(self.meta_folder, '{}.npz'.format(user)))) - - frame_list = glob.glob(os.path.join(self.frame_folder, '{}/*.png'.format(user))) - frame_list = [int(x.split('/')[-1].split('.')[0]) for x in frame_list] - - ref_frame = random.choice(frame_list) - tgt_frame = random.choice(frame_list) - - ref_img = Image.open(os.path.join(self.frame_folder, '{}/{}.png'.format(user, ref_frame))) - tgt_img = Image.open(os.path.join(self.frame_folder, '{}/{}.png'.format(user, tgt_frame))) - - # Make reference and target landmarks - ref_lnd = landmarks.plot_landmarks(landmarks.get_relative_landmarks(meta, ref_frame)) - tgt_lnd = landmarks.plot_landmarks(landmarks.get_relative_landmarks(meta, tgt_frame)) - - ref_img = self.transform(ref_img) - tgt_img = self.transform(tgt_img) - ref_lnd = self.transform(ref_lnd) - tgt_lnd = self.transform(tgt_lnd) - - input_dict = {'ref_img': ref_img, 'tgt_img': tgt_img, 'ref_lnd': ref_lnd, - 'tgt_lnd': tgt_lnd, 'user': user} - - return input_dict - - def __len__(self): - return self.length \ No newline at end of file diff --git a/pix2pixHD/data/landmarks.py b/pix2pixHD/data/landmarks.py deleted file mode 100644 index 0e2544a..0000000 --- a/pix2pixHD/data/landmarks.py +++ /dev/null @@ -1,61 +0,0 @@ -import cv2 -from matplotlib import pyplot as plt -from PIL import Image -import numpy as np - -def get_relative_landmarks(meta, frame_num): - centerx, centery, l = meta['bbox'][frame_num - 1] - orig_height = meta['length'].item() - orig_width = meta['width'].item() - landmarks = meta['landmarks_2d'][frame_num - 1] - - # Go from frame landmarks to cropped and resized frame landmarks - x_left = max(0, centerx-l) - x_right = min(centerx+l, orig_height) - y_up = max(0, centery-l) - y_down = min(centery+l, orig_width) - w = x_right - x_left - h = y_down - y_up - ar_h = 255. / h - ar_w = 255. / w - - landmarks[:,0] -= (centery - l) - landmarks[:,1] -= (centerx - l) - landmarks[:,0] *= ar_h - landmarks[:,1] *= ar_w - - return landmarks - -def plot_landmarks(landmarks): - fig = plt.figure(figsize=(256, 256), dpi=1) - ax = fig.add_subplot(111) - ax.axis('off') - plt.imshow(np.ones((256, 256, 3))) - plt.subplots_adjust(left=0, right=1, top=1, bottom=0) - - lw = 100 - - # Head - ax.plot(landmarks[0:17, 0], landmarks[0:17, 1], linestyle='-', color='green', lw=lw) - # Eyebrows - ax.plot(landmarks[17:22, 0], landmarks[17:22, 1], linestyle='-', color='orange', lw=lw) - ax.plot(landmarks[22:27, 0], landmarks[22:27, 1], linestyle='-', color='orange', lw=lw) - # Nose - ax.plot(landmarks[27:31, 0], landmarks[27:31, 1], linestyle='-', color='blue', lw=lw) - ax.plot(landmarks[31:36, 0], landmarks[31:36, 1], linestyle='-', color='blue', lw=lw) - # Eyes - ax.plot(landmarks[36:42, 0], landmarks[36:42, 1], linestyle='-', color='red', lw=lw) - ax.plot(landmarks[42:48, 0], landmarks[42:48, 1], linestyle='-', color='red', lw=lw) - ax.plot([landmarks[36, 0], landmarks[41, 0]], [landmarks[36, 1], landmarks[41, 1]], - linestyle='-', color='red', lw=lw) - ax.plot([landmarks[42, 0], landmarks[47, 0]], [landmarks[42, 1], landmarks[47, 1]], - linestyle='-', color='red', lw=lw) - # Mouth - ax.plot(landmarks[48:60, 0], landmarks[48:60, 1], linestyle='-', color='purple', lw=lw) - ax.plot([landmarks[48, 0], landmarks[59, 0]], [landmarks[48, 1], landmarks[59, 1]], - linestyle='-', color='purple', lw=lw) - - fig.canvas.draw() - data = Image.frombuffer('RGB', fig.canvas.get_width_height(), fig.canvas.tostring_rgb(), 'raw', 'RGB', 0, 1) - plt.close(fig) - return data \ No newline at end of file diff --git a/pix2pixHD/datasets/avspeech b/pix2pixHD/datasets/avspeech deleted file mode 120000 index 8a340c8..0000000 --- a/pix2pixHD/datasets/avspeech +++ /dev/null @@ -1 +0,0 @@ -/scratch2/avspeech_process/avspeech/ \ No newline at end of file diff --git a/pix2pixHD/datasets/cityscapes b/pix2pixHD/datasets/cityscapes new file mode 120000 index 0000000..b9a55d0 --- /dev/null +++ b/pix2pixHD/datasets/cityscapes @@ -0,0 +1 @@ +/home/grad3/nruiz9/research/fsynth/pix2pixHD_attack/datasets/cityscapes \ No newline at end of file diff --git a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000000_000576_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000000_000576_gtFine_instanceIds.png deleted file mode 100755 index 01da7ed..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000000_000576_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000000_001236_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000000_001236_gtFine_instanceIds.png deleted file mode 100755 index 75506bc..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000000_001236_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000000_003357_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000000_003357_gtFine_instanceIds.png deleted file mode 100755 index 9bd27b0..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000000_003357_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000000_011810_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000000_011810_gtFine_instanceIds.png deleted file mode 100755 index df84eee..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000000_011810_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000000_012868_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000000_012868_gtFine_instanceIds.png deleted file mode 100755 index ba1f7aa..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000000_012868_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_013710_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_013710_gtFine_instanceIds.png deleted file mode 100755 index d05b7db..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_013710_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_015328_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_015328_gtFine_instanceIds.png deleted file mode 100755 index 32d62a3..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_015328_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_023769_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_023769_gtFine_instanceIds.png deleted file mode 100755 index 9eef682..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_023769_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_028335_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_028335_gtFine_instanceIds.png deleted file mode 100755 index b1909d5..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_028335_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_032711_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_032711_gtFine_instanceIds.png deleted file mode 100755 index ac2e293..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_032711_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_033655_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_033655_gtFine_instanceIds.png deleted file mode 100755 index de7328e..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_033655_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_042733_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_042733_gtFine_instanceIds.png deleted file mode 100755 index a98d096..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_042733_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_047552_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_047552_gtFine_instanceIds.png deleted file mode 100755 index ab569e3..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_047552_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_054640_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_054640_gtFine_instanceIds.png deleted file mode 100755 index 5f246a6..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_054640_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_055387_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_055387_gtFine_instanceIds.png deleted file mode 100755 index 2e7d01f..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_inst/frankfurt_000001_055387_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000000_000576_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000000_000576_gtFine_labelIds.png deleted file mode 100755 index 8c9464c..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000000_000576_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000000_001236_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000000_001236_gtFine_labelIds.png deleted file mode 100755 index 9f0ca9f..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000000_001236_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000000_003357_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000000_003357_gtFine_labelIds.png deleted file mode 100755 index 1035e55..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000000_003357_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000000_011810_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000000_011810_gtFine_labelIds.png deleted file mode 100755 index a86913b..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000000_011810_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000000_012868_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000000_012868_gtFine_labelIds.png deleted file mode 100755 index fe81c83..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000000_012868_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_013710_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_013710_gtFine_labelIds.png deleted file mode 100755 index 72b4be4..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_013710_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_015328_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_015328_gtFine_labelIds.png deleted file mode 100755 index afefb6b..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_015328_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_023769_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_023769_gtFine_labelIds.png deleted file mode 100755 index f3af9df..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_023769_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_028335_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_028335_gtFine_labelIds.png deleted file mode 100755 index 5e65e3e..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_028335_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_032711_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_032711_gtFine_labelIds.png deleted file mode 100755 index ba07b73..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_032711_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_033655_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_033655_gtFine_labelIds.png deleted file mode 100755 index 77f519c..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_033655_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_042733_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_042733_gtFine_labelIds.png deleted file mode 100755 index ba08f1d..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_042733_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_047552_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_047552_gtFine_labelIds.png deleted file mode 100755 index 5dff09a..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_047552_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_054640_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_054640_gtFine_labelIds.png deleted file mode 100755 index cb2ab2b..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_054640_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_055387_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_055387_gtFine_labelIds.png deleted file mode 100755 index b00ef7e..0000000 Binary files a/pix2pixHD/datasets/cityscapes/test_label/frankfurt_000001_055387_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_img/aachen_000000_000019_leftImg8bit.png b/pix2pixHD/datasets/cityscapes/train_img/aachen_000000_000019_leftImg8bit.png deleted file mode 100755 index 0e6867e..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_img/aachen_000000_000019_leftImg8bit.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_img/aachen_000001_000019_leftImg8bit.png b/pix2pixHD/datasets/cityscapes/train_img/aachen_000001_000019_leftImg8bit.png deleted file mode 100755 index d5a96ce..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_img/aachen_000001_000019_leftImg8bit.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_img/aachen_000002_000019_leftImg8bit.png b/pix2pixHD/datasets/cityscapes/train_img/aachen_000002_000019_leftImg8bit.png deleted file mode 100755 index 10ce563..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_img/aachen_000002_000019_leftImg8bit.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_img/aachen_000003_000019_leftImg8bit.png b/pix2pixHD/datasets/cityscapes/train_img/aachen_000003_000019_leftImg8bit.png deleted file mode 100755 index 3027fe1..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_img/aachen_000003_000019_leftImg8bit.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_img/aachen_000004_000019_leftImg8bit.png b/pix2pixHD/datasets/cityscapes/train_img/aachen_000004_000019_leftImg8bit.png deleted file mode 100755 index 26945fc..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_img/aachen_000004_000019_leftImg8bit.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_img/aachen_000005_000019_leftImg8bit.png b/pix2pixHD/datasets/cityscapes/train_img/aachen_000005_000019_leftImg8bit.png deleted file mode 100755 index 71fb41f..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_img/aachen_000005_000019_leftImg8bit.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_img/aachen_000006_000019_leftImg8bit.png b/pix2pixHD/datasets/cityscapes/train_img/aachen_000006_000019_leftImg8bit.png deleted file mode 100755 index dd5cf30..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_img/aachen_000006_000019_leftImg8bit.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_img/aachen_000007_000019_leftImg8bit.png b/pix2pixHD/datasets/cityscapes/train_img/aachen_000007_000019_leftImg8bit.png deleted file mode 100755 index 65653d7..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_img/aachen_000007_000019_leftImg8bit.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_inst/aachen_000000_000019_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/train_inst/aachen_000000_000019_gtFine_instanceIds.png deleted file mode 100755 index f4ee222..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_inst/aachen_000000_000019_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_inst/aachen_000001_000019_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/train_inst/aachen_000001_000019_gtFine_instanceIds.png deleted file mode 100755 index dd69137..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_inst/aachen_000001_000019_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_inst/aachen_000002_000019_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/train_inst/aachen_000002_000019_gtFine_instanceIds.png deleted file mode 100755 index bdad5e3..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_inst/aachen_000002_000019_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_inst/aachen_000003_000019_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/train_inst/aachen_000003_000019_gtFine_instanceIds.png deleted file mode 100755 index 91a035b..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_inst/aachen_000003_000019_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_inst/aachen_000004_000019_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/train_inst/aachen_000004_000019_gtFine_instanceIds.png deleted file mode 100755 index 0f5fc70..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_inst/aachen_000004_000019_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_inst/aachen_000005_000019_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/train_inst/aachen_000005_000019_gtFine_instanceIds.png deleted file mode 100755 index ac54ee9..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_inst/aachen_000005_000019_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_inst/aachen_000006_000019_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/train_inst/aachen_000006_000019_gtFine_instanceIds.png deleted file mode 100755 index 49f435f..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_inst/aachen_000006_000019_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_inst/aachen_000007_000019_gtFine_instanceIds.png b/pix2pixHD/datasets/cityscapes/train_inst/aachen_000007_000019_gtFine_instanceIds.png deleted file mode 100755 index 7c5ca0a..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_inst/aachen_000007_000019_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_label/aachen_000000_000019_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/train_label/aachen_000000_000019_gtFine_labelIds.png deleted file mode 100755 index eed7ee6..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_label/aachen_000000_000019_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_label/aachen_000001_000019_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/train_label/aachen_000001_000019_gtFine_labelIds.png deleted file mode 100755 index e9c25ee..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_label/aachen_000001_000019_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_label/aachen_000002_000019_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/train_label/aachen_000002_000019_gtFine_labelIds.png deleted file mode 100755 index c96ab17..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_label/aachen_000002_000019_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_label/aachen_000003_000019_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/train_label/aachen_000003_000019_gtFine_labelIds.png deleted file mode 100755 index da05594..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_label/aachen_000003_000019_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_label/aachen_000004_000019_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/train_label/aachen_000004_000019_gtFine_labelIds.png deleted file mode 100755 index bb30bd9..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_label/aachen_000004_000019_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_label/aachen_000005_000019_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/train_label/aachen_000005_000019_gtFine_labelIds.png deleted file mode 100755 index 958fc40..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_label/aachen_000005_000019_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_label/aachen_000006_000019_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/train_label/aachen_000006_000019_gtFine_labelIds.png deleted file mode 100755 index 89f042e..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_label/aachen_000006_000019_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/datasets/cityscapes/train_label/aachen_000007_000019_gtFine_labelIds.png b/pix2pixHD/datasets/cityscapes/train_label/aachen_000007_000019_gtFine_labelIds.png deleted file mode 100755 index 85b6922..0000000 Binary files a/pix2pixHD/datasets/cityscapes/train_label/aachen_000007_000019_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD/models/pix2pixHD_model.py b/pix2pixHD/models/pix2pixHD_model.py index 1f2d0f7..644aee3 100755 --- a/pix2pixHD/models/pix2pixHD_model.py +++ b/pix2pixHD/models/pix2pixHD_model.py @@ -5,6 +5,7 @@ from torch.autograd import Variable from util.image_pool import ImagePool from .base_model import BaseModel from . import networks +from util import attacks class Pix2PixHDModel(BaseModel): def name(self): @@ -27,7 +28,11 @@ class Pix2PixHDModel(BaseModel): ##### define networks # Generator network - netG_input_nc = 6 + netG_input_nc = input_nc + if not opt.no_instance: + netG_input_nc += 1 + if self.use_features: + netG_input_nc += opt.feat_num self.netG = networks.define_G(netG_input_nc, opt.output_nc, opt.ngf, opt.netG, opt.n_downsample_global, opt.n_blocks_global, opt.n_local_enhancers, opt.n_blocks_local, opt.norm, gpu_ids=self.gpu_ids) @@ -35,7 +40,9 @@ class Pix2PixHDModel(BaseModel): # Discriminator network if self.isTrain: use_sigmoid = opt.no_lsgan - netD_input_nc = 6 + netD_input_nc = input_nc + opt.output_nc + if not opt.no_instance: + netD_input_nc += 1 self.netD = networks.define_D(netD_input_nc, opt.ndf, opt.n_layers_D, opt.norm, use_sigmoid, opt.num_D, not opt.no_ganFeat_loss, gpu_ids=self.gpu_ids) @@ -135,29 +142,39 @@ class Pix2PixHDModel(BaseModel): return input_label, inst_map, real_image, feat_map - def discriminate(self, tgt_lnd, test_image, use_pool=False): - input_concat = torch.cat((tgt_lnd, test_image.detach()), dim=1) + def discriminate(self, input_label, test_image, use_pool=False): + input_concat = torch.cat((input_label, test_image.detach()), dim=1) if use_pool: fake_query = self.fake_pool.query(input_concat) return self.netD.forward(fake_query) else: return self.netD.forward(input_concat) - def forward(self, tgt_lnd, ref_img, tgt_img, infer=False): - # Fake Generation - input_concat = torch.cat((tgt_lnd, ref_img), dim=1) + def forward(self, label, inst, image, feat, infer=False): + # Encode Inputs + input_label, inst_map, real_image, feat_map = self.encode_input(label, inst, image, feat) + + # Fake Generation + if self.use_features: + if not self.opt.load_features: + feat_map = self.netE.forward(real_image, inst_map) + input_concat = torch.cat((input_label, feat_map), dim=1) + else: + input_concat = input_label + fake_image = self.netG.forward(input_concat) + # fake_image = self.netG.forward(input_adv) # Fake Detection and Loss - pred_fake_pool = self.discriminate(tgt_lnd, fake_image, use_pool=True) + pred_fake_pool = self.discriminate(input_label, fake_image, use_pool=True) loss_D_fake = self.criterionGAN(pred_fake_pool, False) # Real Detection and Loss - pred_real = self.discriminate(tgt_lnd, tgt_img) + pred_real = self.discriminate(input_label, real_image) loss_D_real = self.criterionGAN(pred_real, True) # GAN loss (Fake Passability Loss) - pred_fake = self.netD.forward(torch.cat((tgt_lnd, fake_image), dim=1)) + pred_fake = self.netD.forward(torch.cat((input_label, fake_image), dim=1)) loss_G_GAN = self.criterionGAN(pred_fake, True) # GAN feature matching loss @@ -168,24 +185,86 @@ class Pix2PixHDModel(BaseModel): for i in range(self.opt.num_D): for j in range(len(pred_fake[i])-1): loss_G_GAN_Feat += D_weights * feat_weights * \ - self.criterionFeat(pred_fake[i][j], pred_real[i][j].detach()) * self.opt.lambda_feat * 0.1 + self.criterionFeat(pred_fake[i][j], pred_real[i][j].detach()) * self.opt.lambda_feat # VGG feature matching loss loss_G_VGG = 0 if not self.opt.no_vgg_loss: - loss_G_VGG = self.criterionVGG(fake_image, tgt_img) * self.opt.lambda_feat + loss_G_VGG = self.criterionVGG(fake_image, real_image) * self.opt.lambda_feat # Only return the fake_B image if necessary to save BW return [ self.loss_filter( loss_G_GAN, loss_G_GAN_Feat, loss_G_VGG, loss_D_real, loss_D_fake ), None if not infer else fake_image ] - def inference(self, tgt_lnd, ref_img): + def inference(self, label, inst, image=None): + # Encode Inputs + image = Variable(image) if image is not None else None + input_label, inst_map, real_image, _ = self.encode_input(Variable(label), Variable(inst), image, infer=True) + # Fake Generation - input_concat = torch.cat((tgt_lnd, ref_img), dim=1) + if self.use_features: + if self.opt.use_encoded_image: + # encode the real image to get feature map + feat_map = self.netE.forward(real_image, inst_map) + else: + # sample clusters from precomputed features + feat_map = self.sample_features(inst_map) + input_concat = torch.cat((input_label, feat_map), dim=1) + else: + input_concat = input_label + with torch.no_grad(): fake_image = self.netG.forward(input_concat) - + return fake_image + def inference_attack(self, label, inst, image=None, perturb=None): + # Encode Inputs + image = Variable(image) if image is not None else None + input_label, inst_map, real_image, _ = self.encode_input(Variable(label), Variable(inst), image, infer=True) + + # Fake Generation + if self.use_features: + if self.opt.use_encoded_image: + # encode the real image to get feature map + feat_map = self.netE.forward(real_image, inst_map) + else: + # sample clusters from precomputed features + feat_map = self.sample_features(inst_map) + input_concat = torch.cat((input_label, feat_map), dim=1) + else: + input_concat = input_label + + input_adv = torch.clamp(input_concat + perturb, min=-1, max=1) + + with torch.no_grad(): + fake_image = self.netG.forward(input_adv) + + return fake_image, input_adv + + def attack(self, label, inst, image=None, target=None): + # Encode Inputs + image = Variable(image) if image is not None else None + input_label, inst_map, real_image, _ = self.encode_input(Variable(label), Variable(inst), image, infer=True) + + # Fake Generation + if self.use_features: + if self.opt.use_encoded_image: + # encode the real image to get feature map + feat_map = self.netE.forward(real_image, inst_map) + else: + # sample clusters from precomputed features + feat_map = self.sample_features(inst_map) + input_concat = torch.cat((input_label, feat_map), dim=1) + else: + input_concat = input_label + + # Attack + pgd_attack = attacks.LinfPGDAttack(model=self.netG) + + input_adv, perturb = pgd_attack.perturb(input_concat, target) + + return input_adv, perturb + def sample_features(self, inst): # read precomputed feature clusters cluster_path = os.path.join(self.opt.checkpoints_dir, self.opt.name, self.opt.cluster_path) @@ -231,6 +310,7 @@ class Pix2PixHDModel(BaseModel): def get_edges(self, t): edge = torch.cuda.ByteTensor(t.size()).zero_() + edge = edge.bool() edge[:,:,:,1:] = edge[:,:,:,1:] | (t[:,:,:,1:] != t[:,:,:,:-1]) edge[:,:,:,:-1] = edge[:,:,:,:-1] | (t[:,:,:,1:] != t[:,:,:,:-1]) edge[:,:,1:,:] = edge[:,:,1:,:] | (t[:,:,1:,:] != t[:,:,:-1,:]) diff --git a/pix2pixHD/options/base_options.py b/pix2pixHD/options/base_options.py index d64fcaa..0d5e769 100755 --- a/pix2pixHD/options/base_options.py +++ b/pix2pixHD/options/base_options.py @@ -10,7 +10,7 @@ class BaseOptions(): def initialize(self): # experiment specifics - self.parser.add_argument('--name', type=str, default='fsynth', help='name of the experiment. It decides where to store samples and models') + self.parser.add_argument('--name', type=str, default='label2city', help='name of the experiment. It decides where to store samples and models') self.parser.add_argument('--gpu_ids', type=str, default='0', help='gpu ids: e.g. 0 0,1,2, 0,2. use -1 for CPU') self.parser.add_argument('--checkpoints_dir', type=str, default='./checkpoints', help='models are saved here') self.parser.add_argument('--model', type=str, default='pix2pixHD', help='which model to use') @@ -22,10 +22,10 @@ class BaseOptions(): self.parser.add_argument('--local_rank', type=int, default=0, help='local rank for distributed training') # input/output sizes - self.parser.add_argument('--batchSize', type=int, default=8, help='input batch size') - self.parser.add_argument('--loadSize', type=int, default=256, help='scale images to this size') - self.parser.add_argument('--fineSize', type=int, default=128, help='then crop to this size') - self.parser.add_argument('--label_nc', type=int, default=3, help='# of input label channels') + self.parser.add_argument('--batchSize', type=int, default=1, help='input batch size') + self.parser.add_argument('--loadSize', type=int, default=1024, help='scale images to this size') + self.parser.add_argument('--fineSize', type=int, default=512, help='then crop to this size') + self.parser.add_argument('--label_nc', type=int, default=35, help='# of input label channels') self.parser.add_argument('--input_nc', type=int, default=3, help='# of input image channels') self.parser.add_argument('--output_nc', type=int, default=3, help='# of output image channels') diff --git a/pix2pixHD/options/test_options.py b/pix2pixHD/options/test_options.py index f27fc5e..5604347 100755 --- a/pix2pixHD/options/test_options.py +++ b/pix2pixHD/options/test_options.py @@ -8,7 +8,7 @@ class TestOptions(BaseOptions): self.parser.add_argument('--aspect_ratio', type=float, default=1.0, help='aspect ratio of result images') self.parser.add_argument('--phase', type=str, default='test', help='train, val, test, etc') self.parser.add_argument('--which_epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') - self.parser.add_argument('--how_many', type=int, default=50, help='how many test images to run') + self.parser.add_argument('--how_many', type=int, default=100, help='how many test images to run') self.parser.add_argument('--cluster_path', type=str, default='features_clustered_010.npy', help='the path for clustered results of encoded features') self.parser.add_argument('--use_encoded_image', action='store_true', help='if specified, encode the real image to get the feature map') self.parser.add_argument("--export_onnx", type=str, help="export ONNX model to a given file") diff --git a/pix2pixHD/options/train_options.py b/pix2pixHD/options/train_options.py index 0a20057..cacb8e7 100755 --- a/pix2pixHD/options/train_options.py +++ b/pix2pixHD/options/train_options.py @@ -25,7 +25,7 @@ class TrainOptions(BaseOptions): self.parser.add_argument('--num_D', type=int, default=2, help='number of discriminators to use') self.parser.add_argument('--n_layers_D', type=int, default=3, help='only used if which_model_netD==n_layers') self.parser.add_argument('--ndf', type=int, default=64, help='# of discrim filters in first conv layer') - self.parser.add_argument('--lambda_feat', type=float, default=100.0, help='weight for feature matching loss') + self.parser.add_argument('--lambda_feat', type=float, default=10.0, help='weight for feature matching loss') self.parser.add_argument('--no_ganFeat_loss', action='store_true', help='if specified, do *not* use discriminator feature matching loss') self.parser.add_argument('--no_vgg_loss', action='store_true', help='if specified, do *not* use VGG feature matching loss') self.parser.add_argument('--no_lsgan', action='store_true', help='do *not* use least square GAN, if false, use vanilla GAN') diff --git a/pix2pixHD/results b/pix2pixHD/results index d5ed8d1..16230fd 120000 --- a/pix2pixHD/results +++ b/pix2pixHD/results @@ -1 +1 @@ -/scratch2/fsynth/results \ No newline at end of file +/home/grad3/nruiz9/research/fsynth/pix2pixHD_attack/results \ No newline at end of file diff --git a/pix2pixHD/test.py b/pix2pixHD/test.py index e0b1ec3..062007d 100755 --- a/pix2pixHD/test.py +++ b/pix2pixHD/test.py @@ -8,6 +8,7 @@ import util.util as util from util.visualizer import Visualizer from util import html import torch +import torch.nn.functional as F opt = TestOptions().parse(save=False) opt.nThreads = 1 # test code only supports nThreads = 1 @@ -34,6 +35,10 @@ if not opt.engine and not opt.onnx: print(model) else: from run_engine import run_trt_engine, run_onnx + +# Initialize Metrics +l1_error, l2_error, min_dist, l0_error = 0.0, 0.0, 0.0, 0.0 +n_dist, n_samples = 0, 0 for i, data in enumerate(dataset): if i >= opt.how_many: @@ -51,17 +56,45 @@ for i, data in enumerate(dataset): opt.export_onnx, verbose=True) exit(0) minibatch = 1 + + print(i) + if opt.engine: generated = run_trt_engine(opt.engine, minibatch, [data['label'], data['inst']]) elif opt.onnx: generated = run_onnx(opt.onnx, opt.data_type, minibatch, [data['label'], data['inst']]) - else: - generated = model.inference(data['label'], data['inst'], data['image']) + else: + # Get ground-truth output + with torch.no_grad(): + generated_noattack = model.inference(data['label'], data['inst'], data['image']) + # Attack + adv_image, perturb = model.attack(data['label'], data['inst'], data['image'], target=generated_noattack) + # Get output from adversarial sample + with torch.no_grad(): + generated, adv_img = model.inference_attack(data['label'], data['inst'], data['image'], perturb) - visuals = OrderedDict([('input_label', util.tensor2label(data['label'][0], opt.label_nc)), - ('synthesized_image', util.tensor2im(generated.data[0]))]) + visuals = OrderedDict([('original_label', util.tensor2label(data['label'][0], opt.label_nc)), + ('input_label', util.tensor2label(adv_img.data[0], opt.label_nc)), + ('attacked_image', util.tensor2im(generated.data[0])), + ('noattack', util.tensor2im(generated_noattack.data[0]))]) img_path = data['path'] print('process image... %s' % img_path) visualizer.save_images(webpage, visuals, img_path) + # Compute metrics + l1_error += F.l1_loss(generated, generated_noattack) + l2_error += F.mse_loss(generated, generated_noattack) + l0_error += (generated - generated_noattack).norm(0) + min_dist += (generated - generated_noattack).norm(float('-inf')) + if F.mse_loss(generated, generated_noattack) > 0.05: + n_dist += 1 + n_samples += 1 + + generated, genereated_noattack, adv_image, adv_img, perturb = None, None, None, None, None + data = None + +# Print metrics +print('{} images. L1 error: {}. L2 error: {}. prop_dist: {}. L0 error: {}. L_-inf error: {}.'.format(n_samples, +l1_error / n_samples, l2_error / n_samples, float(n_dist) / n_samples, l0_error / n_samples, min_dist / n_samples)) + webpage.save() diff --git a/pix2pixHD/train_fsynth.py b/pix2pixHD/train_fsynth.py deleted file mode 100755 index 7f827ea..0000000 --- a/pix2pixHD/train_fsynth.py +++ /dev/null @@ -1,153 +0,0 @@ -import time -import os -import numpy as np -import torch -from torch.autograd import Variable -from collections import OrderedDict -from subprocess import call -import fractions -def lcm(a,b): return abs(a * b)/fractions.gcd(a,b) if a and b else 0 - -from options.train_options import TrainOptions -from data.data_loader import CreateDataLoader -from models.models import create_model -import util.util as util -from util.visualizer import Visualizer -from torchvision import transforms - -from data import avspeech -from torch.utils.data import DataLoader - -opt = TrainOptions().parse() -iter_path = os.path.join(opt.checkpoints_dir, opt.name, 'iter.txt') -if opt.continue_train: - try: - start_epoch, epoch_iter = np.loadtxt(iter_path , delimiter=',', dtype=int) - except: - start_epoch, epoch_iter = 1, 0 - print('Resuming from epoch %d at iteration %d' % (start_epoch, epoch_iter)) -else: - start_epoch, epoch_iter = 1, 0 - -opt.print_freq = lcm(opt.print_freq, opt.batchSize) -if opt.debug: - opt.display_freq = 1 - opt.print_freq = 1 - opt.niter = 1 - opt.niter_decay = 0 - opt.max_dataset_size = 10 - -transform = transforms.Compose([ - transforms.ToTensor(), - transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) - ]) - -dataset = avspeech.AVSpeech(transform) -loader = DataLoader(dataset, batch_size=opt.batchSize, shuffle=True, num_workers=2) -dataset_size = len(dataset) -print('#training images = %d' % dataset_size) - -model = create_model(opt) -visualizer = Visualizer(opt) -if opt.fp16: - from apex import amp - model, [optimizer_G, optimizer_D] = amp.initialize(model, [model.optimizer_G, model.optimizer_D], opt_level='O1') - model = torch.nn.DataParallel(model, device_ids=opt.gpu_ids) -else: - optimizer_G, optimizer_D = model.module.optimizer_G, model.module.optimizer_D - -total_steps = (start_epoch-1) * dataset_size + epoch_iter - -display_delta = total_steps % opt.display_freq -print_delta = total_steps % opt.print_freq -save_delta = total_steps % opt.save_latest_freq - -for epoch in range(start_epoch, 1000): - epoch_start_time = time.time() - if epoch != start_epoch: - epoch_iter = epoch_iter % dataset_size - - # 100 per epoch - for _ in range(1000): - for i, data in enumerate(loader, start=epoch_iter): - if total_steps % opt.print_freq == print_delta: - iter_start_time = time.time() - total_steps += opt.batchSize - epoch_iter += opt.batchSize - - # whether to collect output images - save_fake = total_steps % opt.display_freq == display_delta - - ############## Forward Pass ###################### - losses, generated = model(data['tgt_lnd'], data['ref_img'], data['tgt_img'], infer=save_fake) - - # sum per device losses - losses = [ torch.mean(x) if not isinstance(x, int) else x for x in losses ] - loss_dict = dict(zip(model.module.loss_names, losses)) - - # calculate final loss scalar - loss_D = (loss_dict['D_fake'] + loss_dict['D_real']) * 0.5 - loss_G = loss_dict['G_GAN'] + loss_dict.get('G_GAN_Feat',0) + loss_dict.get('G_VGG',0) - - ############### Backward Pass #################### - # update generator weights - optimizer_G.zero_grad() - if opt.fp16: - with amp.scale_loss(loss_G, optimizer_G) as scaled_loss: scaled_loss.backward() - else: - loss_G.backward() - optimizer_G.step() - - # update discriminator weights - optimizer_D.zero_grad() - if opt.fp16: - with amp.scale_loss(loss_D, optimizer_D) as scaled_loss: scaled_loss.backward() - else: - loss_D.backward() - optimizer_D.step() - - ############## Display results and errors ########## - ### print out errors - if total_steps % opt.print_freq == print_delta: - errors = {k: v.data.item() if not isinstance(v, int) else v for k, v in loss_dict.items()} - t = (time.time() - iter_start_time) / opt.print_freq - visualizer.print_current_errors(epoch, epoch_iter, errors, t) - visualizer.plot_current_errors(errors, total_steps) - #call(["nvidia-smi", "--format=csv", "--query-gpu=memory.used,memory.free"]) - - ### display output images - if save_fake: - visuals = OrderedDict([('ref_img', util.tensor2im(data['ref_img'].data[0])), - ('tgt_lnd', util.tensor2im(data['tgt_lnd'][0])), - ('synth_img', util.tensor2im(generated.data[0])), - ('tgt_img', util.tensor2im(data['tgt_img'][0]))]) - visualizer.display_current_results(visuals, epoch, total_steps) - - ### save latest model - if total_steps % opt.save_latest_freq == save_delta: - print('saving the latest model (epoch %d, total_steps %d)' % (epoch, total_steps)) - model.module.save('latest') - np.savetxt(iter_path, (epoch, epoch_iter), delimiter=',', fmt='%d') - - if epoch_iter >= dataset_size: - break - - # end of epoch - iter_end_time = time.time() - print('End of epoch %d / %d \t Time Taken: %d sec' % - (epoch, opt.niter + opt.niter_decay, time.time() - epoch_start_time)) - - ### save model for this epoch - if epoch % opt.save_epoch_freq == 0: - print('saving the model at the end of epoch %d, iters %d' % (epoch, total_steps)) - model.module.save('latest') - model.module.save(epoch) - np.savetxt(iter_path, (epoch+1, 0), delimiter=',', fmt='%d') - - ### instead of only training the local enhancer, train the entire network after certain iterations - if (opt.niter_fix_global != 0) and (epoch == opt.niter_fix_global): - model.module.update_fixed_params() - - ### linearly decay learning rate after certain iterations - if epoch > opt.niter: - model.module.update_learning_rate() diff --git a/pix2pixHD_attack/util/attacks.py b/pix2pixHD/util/attacks.py similarity index 53% rename from pix2pixHD_attack/util/attacks.py rename to pix2pixHD/util/attacks.py index d43c61c..e78778e 100644 --- a/pix2pixHD_attack/util/attacks.py +++ b/pix2pixHD/util/attacks.py @@ -7,27 +7,41 @@ import torch import torch.nn as nn class LinfPGDAttack(object): - def __init__(self, model=None, epsilon=0.05, k=20, a=0.01): + def __init__(self, model=None, epsilon=0.05, k=10, a=0.01): + """ + FGSM, I-FGSM and PGD attacks + epsilon: magnitude of attack + k: iterations + a: step size + """ self.model = model self.epsilon = epsilon self.k = k self.a = a self.loss_fn = nn.MSELoss() + # PGD or I-FGSM? + self.rand = True + def perturb(self, X_nat, y): """ - Given examples (X_nat, y), returns adversarial - examples within epsilon of X_nat in l_infinity norm. + Vanilla Attack. """ - X = X_nat.clone().detach_() + if self.rand: + X = X_nat.clone().detach_() + torch.tensor(np.random.uniform(-self.epsilon, self.epsilon, X_nat.shape).astype('float32')).cuda() + else: + X = X_nat.clone().detach_() + # use the following if FGSM or I-FGSM and random seeds are fixed + X = X_nat.clone().detach_() + torch.tensor(np.random.uniform(-0.001, 0.001, X_nat.shape).astype('float32')).cuda() for i in range(self.k): - print('test', i) X.requires_grad = True output = self.model(X) self.model.zero_grad() - loss = -self.loss_fn(output, y) + + # Minus in the loss means "towards" and plus means "away from" + loss = self.loss_fn(output, y) loss.backward() grad = X.grad @@ -35,8 +49,8 @@ class LinfPGDAttack(object): eta = torch.clamp(X_adv - X_nat, min=-self.epsilon, max=self.epsilon) X = torch.clamp(X_nat + eta, min=-1, max=1).detach_() - eta = None - X_adv = None + + eta, X_adv, loss, grad, output = None, None, None, None, None return X, X - X_nat diff --git a/pix2pixHD_attack/._LICENSE.txt b/pix2pixHD_attack/._LICENSE.txt deleted file mode 100644 index b58f854..0000000 Binary files a/pix2pixHD_attack/._LICENSE.txt and /dev/null differ diff --git a/pix2pixHD_attack/._README.md b/pix2pixHD_attack/._README.md deleted file mode 100644 index 9d8f3c8..0000000 Binary files a/pix2pixHD_attack/._README.md and /dev/null differ diff --git a/pix2pixHD_attack/._encode_features.py b/pix2pixHD_attack/._encode_features.py deleted file mode 100644 index 4f41162..0000000 Binary files a/pix2pixHD_attack/._encode_features.py and /dev/null differ diff --git a/pix2pixHD_attack/._precompute_feature_maps.py b/pix2pixHD_attack/._precompute_feature_maps.py deleted file mode 100644 index b014bf7..0000000 Binary files a/pix2pixHD_attack/._precompute_feature_maps.py and /dev/null differ diff --git a/pix2pixHD_attack/._run_engine.py b/pix2pixHD_attack/._run_engine.py deleted file mode 100644 index d17effe..0000000 Binary files a/pix2pixHD_attack/._run_engine.py and /dev/null differ diff --git a/pix2pixHD_attack/._test.py b/pix2pixHD_attack/._test.py deleted file mode 100644 index b23fc58..0000000 Binary files a/pix2pixHD_attack/._test.py and /dev/null differ diff --git a/pix2pixHD_attack/._train.py b/pix2pixHD_attack/._train.py deleted file mode 100644 index 0d41fae..0000000 Binary files a/pix2pixHD_attack/._train.py and /dev/null differ diff --git a/pix2pixHD_attack/.gitignore b/pix2pixHD_attack/.gitignore deleted file mode 100755 index 681efd0..0000000 --- a/pix2pixHD_attack/.gitignore +++ /dev/null @@ -1,40 +0,0 @@ -debug* -checkpoints/ -results/ -build/ -dist/ -torch.egg-info/ -*/**/__pycache__ -torch/version.py -torch/csrc/generic/TensorMethods.cpp -torch/lib/*.so* -torch/lib/*.dylib* -torch/lib/*.h -torch/lib/build -torch/lib/tmp_install -torch/lib/include -torch/lib/torch_shm_manager -torch/csrc/cudnn/cuDNN.cpp -torch/csrc/nn/THNN.cwrap -torch/csrc/nn/THNN.cpp -torch/csrc/nn/THCUNN.cwrap -torch/csrc/nn/THCUNN.cpp -torch/csrc/nn/THNN_generic.cwrap -torch/csrc/nn/THNN_generic.cpp -torch/csrc/nn/THNN_generic.h -docs/src/**/* -test/data/legacy_modules.t7 -test/data/gpu_tensors.pt -test/htmlcov -test/.coverage -*/*.pyc -*/**/*.pyc -*/**/**/*.pyc -*/**/**/**/*.pyc -*/**/**/**/**/*.pyc -*/*.so* -*/**/*.so* -*/**/*.dylib* -test/data/legacy_serialized.pt -*.DS_Store -*~ diff --git a/pix2pixHD_attack/LICENSE.txt b/pix2pixHD_attack/LICENSE.txt deleted file mode 100755 index 091b42f..0000000 --- a/pix2pixHD_attack/LICENSE.txt +++ /dev/null @@ -1,45 +0,0 @@ -Copyright (C) 2019 NVIDIA Corporation. Ting-Chun Wang, Ming-Yu Liu, Jun-Yan Zhu. -BSD License. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ANY PARTICULAR PURPOSE. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL -DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, -WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING -OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - - ---------------------------- LICENSE FOR pytorch-CycleGAN-and-pix2pix ---------------- -Copyright (c) 2017, Jun-Yan Zhu and Taesung Park -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pix2pixHD_attack/README.md b/pix2pixHD_attack/README.md deleted file mode 100755 index 7c3315c..0000000 --- a/pix2pixHD_attack/README.md +++ /dev/null @@ -1,144 +0,0 @@ - - -



- -# pix2pixHD -### [Project](https://tcwang0509.github.io/pix2pixHD/) | [Youtube](https://youtu.be/3AIpPlzM_qs) | [Paper](https://arxiv.org/pdf/1711.11585.pdf)
-Pytorch implementation of our method for high-resolution (e.g. 2048x1024) photorealistic image-to-image translation. It can be used for turning semantic label maps into photo-realistic images or synthesizing portraits from face label maps.

-[High-Resolution Image Synthesis and Semantic Manipulation with Conditional GANs](https://tcwang0509.github.io/pix2pixHD/) - [Ting-Chun Wang](https://tcwang0509.github.io/)1, [Ming-Yu Liu](http://mingyuliu.net/)1, [Jun-Yan Zhu](http://people.eecs.berkeley.edu/~junyanz/)2, Andrew Tao1, [Jan Kautz](http://jankautz.com/)1, [Bryan Catanzaro](http://catanzaro.name/)1 - 1NVIDIA Corporation, 2UC Berkeley - In CVPR 2018. - -## Image-to-image translation at 2k/1k resolution -- Our label-to-streetview results -

- - -

-- Interactive editing results -

- - -

-- Additional streetview results -

- - -

-

- - -

- -- Label-to-face and interactive editing results -

- - - -

-

- - - -

- -- Our editing interface -

- - -

- -## Prerequisites -- Linux or macOS -- Python 2 or 3 -- NVIDIA GPU (11G memory or larger) + CUDA cuDNN - -## Getting Started -### Installation -- Install PyTorch and dependencies from http://pytorch.org -- Install python libraries [dominate](https://github.com/Knio/dominate). -```bash -pip install dominate -``` -- Clone this repo: -```bash -git clone https://github.com/NVIDIA/pix2pixHD -cd pix2pixHD -``` - - -### Testing -- A few example Cityscapes test images are included in the `datasets` folder. -- Please download the pre-trained Cityscapes model from [here](https://drive.google.com/file/d/1h9SykUnuZul7J3Nbms2QGH1wa85nbN2-/view?usp=sharing) (google drive link), and put it under `./checkpoints/label2city_1024p/` -- Test the model (`bash ./scripts/test_1024p.sh`): -```bash -#!./scripts/test_1024p.sh -python test.py --name label2city_1024p --netG local --ngf 32 --resize_or_crop none -``` -The test results will be saved to a html file here: `./results/label2city_1024p/test_latest/index.html`. - -More example scripts can be found in the `scripts` directory. - - -### Dataset -- We use the Cityscapes dataset. To train a model on the full dataset, please download it from the [official website](https://www.cityscapes-dataset.com/) (registration required). -After downloading, please put it under the `datasets` folder in the same way the example images are provided. - - -### Training -- Train a model at 1024 x 512 resolution (`bash ./scripts/train_512p.sh`): -```bash -#!./scripts/train_512p.sh -python train.py --name label2city_512p -``` -- To view training results, please checkout intermediate results in `./checkpoints/label2city_512p/web/index.html`. -If you have tensorflow installed, you can see tensorboard logs in `./checkpoints/label2city_512p/logs` by adding `--tf_log` to the training scripts. - -### Multi-GPU training -- Train a model using multiple GPUs (`bash ./scripts/train_512p_multigpu.sh`): -```bash -#!./scripts/train_512p_multigpu.sh -python train.py --name label2city_512p --batchSize 8 --gpu_ids 0,1,2,3,4,5,6,7 -``` -Note: this is not tested and we trained our model using single GPU only. Please use at your own discretion. - -### Training with Automatic Mixed Precision (AMP) for faster speed -- To train with mixed precision support, please first install apex from: https://github.com/NVIDIA/apex -- You can then train the model by adding `--fp16`. For example, -```bash -#!./scripts/train_512p_fp16.sh -python -m torch.distributed.launch train.py --name label2city_512p --fp16 -``` -In our test case, it trains about 80% faster with AMP on a Volta machine. - -### Training at full resolution -- To train the images at full resolution (2048 x 1024) requires a GPU with 24G memory (`bash ./scripts/train_1024p_24G.sh`), or 16G memory if using mixed precision (AMP). -- If only GPUs with 12G memory are available, please use the 12G script (`bash ./scripts/train_1024p_12G.sh`), which will crop the images during training. Performance is not guaranteed using this script. - -### Training with your own dataset -- If you want to train with your own dataset, please generate label maps which are one-channel whose pixel values correspond to the object labels (i.e. 0,1,...,N-1, where N is the number of labels). This is because we need to generate one-hot vectors from the label maps. Please also specity `--label_nc N` during both training and testing. -- If your input is not a label map, please just specify `--label_nc 0` which will directly use the RGB colors as input. The folders should then be named `train_A`, `train_B` instead of `train_label`, `train_img`, where the goal is to translate images from A to B. -- If you don't have instance maps or don't want to use them, please specify `--no_instance`. -- The default setting for preprocessing is `scale_width`, which will scale the width of all training images to `opt.loadSize` (1024) while keeping the aspect ratio. If you want a different setting, please change it by using the `--resize_or_crop` option. For example, `scale_width_and_crop` first resizes the image to have width `opt.loadSize` and then does random cropping of size `(opt.fineSize, opt.fineSize)`. `crop` skips the resizing step and only performs random cropping. If you don't want any preprocessing, please specify `none`, which will do nothing other than making sure the image is divisible by 32. - -## More Training/Test Details -- Flags: see `options/train_options.py` and `options/base_options.py` for all the training flags; see `options/test_options.py` and `options/base_options.py` for all the test flags. -- Instance map: we take in both label maps and instance maps as input. If you don't want to use instance maps, please specify the flag `--no_instance`. - - -## Citation - -If you find this useful for your research, please use the following. - -``` -@inproceedings{wang2018pix2pixHD, - title={High-Resolution Image Synthesis and Semantic Manipulation with Conditional GANs}, - author={Ting-Chun Wang and Ming-Yu Liu and Jun-Yan Zhu and Andrew Tao and Jan Kautz and Bryan Catanzaro}, - booktitle={Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition}, - year={2018} -} -``` - -## Acknowledgments -This code borrows heavily from [pytorch-CycleGAN-and-pix2pix](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix). diff --git a/pix2pixHD_attack/_config.yml b/pix2pixHD_attack/_config.yml deleted file mode 100755 index 2f7efbe..0000000 --- a/pix2pixHD_attack/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-minimal \ No newline at end of file diff --git a/pix2pixHD_attack/checkpoints b/pix2pixHD_attack/checkpoints deleted file mode 120000 index 5c90d1e..0000000 --- a/pix2pixHD_attack/checkpoints +++ /dev/null @@ -1 +0,0 @@ -/scratch2/fsynth/checkpoints \ No newline at end of file diff --git a/pix2pixHD_attack/data/__init__.py b/pix2pixHD_attack/data/__init__.py deleted file mode 100755 index e69de29..0000000 diff --git a/pix2pixHD_attack/data/aligned_dataset.py b/pix2pixHD_attack/data/aligned_dataset.py deleted file mode 100755 index 29785c1..0000000 --- a/pix2pixHD_attack/data/aligned_dataset.py +++ /dev/null @@ -1,76 +0,0 @@ -import os.path -from data.base_dataset import BaseDataset, get_params, get_transform, normalize -from data.image_folder import make_dataset -from PIL import Image - -class AlignedDataset(BaseDataset): - def initialize(self, opt): - self.opt = opt - self.root = opt.dataroot - - ### input A (label maps) - dir_A = '_A' if self.opt.label_nc == 0 else '_label' - self.dir_A = os.path.join(opt.dataroot, opt.phase + dir_A) - self.A_paths = sorted(make_dataset(self.dir_A)) - - ### input B (real images) - if opt.isTrain or opt.use_encoded_image: - dir_B = '_B' if self.opt.label_nc == 0 else '_img' - self.dir_B = os.path.join(opt.dataroot, opt.phase + dir_B) - self.B_paths = sorted(make_dataset(self.dir_B)) - - ### instance maps - if not opt.no_instance: - self.dir_inst = os.path.join(opt.dataroot, opt.phase + '_inst') - self.inst_paths = sorted(make_dataset(self.dir_inst)) - - ### load precomputed instance-wise encoded features - if opt.load_features: - self.dir_feat = os.path.join(opt.dataroot, opt.phase + '_feat') - print('----------- loading features from %s ----------' % self.dir_feat) - self.feat_paths = sorted(make_dataset(self.dir_feat)) - - self.dataset_size = len(self.A_paths) - - def __getitem__(self, index): - ### input A (label maps) - A_path = self.A_paths[index] - A = Image.open(A_path) - params = get_params(self.opt, A.size) - if self.opt.label_nc == 0: - transform_A = get_transform(self.opt, params) - A_tensor = transform_A(A.convert('RGB')) - else: - transform_A = get_transform(self.opt, params, method=Image.NEAREST, normalize=False) - A_tensor = transform_A(A) * 255.0 - - B_tensor = inst_tensor = feat_tensor = 0 - ### input B (real images) - if self.opt.isTrain or self.opt.use_encoded_image: - B_path = self.B_paths[index] - B = Image.open(B_path).convert('RGB') - transform_B = get_transform(self.opt, params) - B_tensor = transform_B(B) - - ### if using instance maps - if not self.opt.no_instance: - inst_path = self.inst_paths[index] - inst = Image.open(inst_path) - inst_tensor = transform_A(inst) - - if self.opt.load_features: - feat_path = self.feat_paths[index] - feat = Image.open(feat_path).convert('RGB') - norm = normalize() - feat_tensor = norm(transform_A(feat)) - - input_dict = {'label': A_tensor, 'inst': inst_tensor, 'image': B_tensor, - 'feat': feat_tensor, 'path': A_path} - - return input_dict - - def __len__(self): - return len(self.A_paths) // self.opt.batchSize * self.opt.batchSize - - def name(self): - return 'AlignedDataset' \ No newline at end of file diff --git a/pix2pixHD_attack/data/base_data_loader.py b/pix2pixHD_attack/data/base_data_loader.py deleted file mode 100755 index 0e1deb5..0000000 --- a/pix2pixHD_attack/data/base_data_loader.py +++ /dev/null @@ -1,14 +0,0 @@ - -class BaseDataLoader(): - def __init__(self): - pass - - def initialize(self, opt): - self.opt = opt - pass - - def load_data(): - return None - - - diff --git a/pix2pixHD_attack/data/base_dataset.py b/pix2pixHD_attack/data/base_dataset.py deleted file mode 100755 index ece8813..0000000 --- a/pix2pixHD_attack/data/base_dataset.py +++ /dev/null @@ -1,90 +0,0 @@ -import torch.utils.data as data -from PIL import Image -import torchvision.transforms as transforms -import numpy as np -import random - -class BaseDataset(data.Dataset): - def __init__(self): - super(BaseDataset, self).__init__() - - def name(self): - return 'BaseDataset' - - def initialize(self, opt): - pass - -def get_params(opt, size): - w, h = size - new_h = h - new_w = w - if opt.resize_or_crop == 'resize_and_crop': - new_h = new_w = opt.loadSize - elif opt.resize_or_crop == 'scale_width_and_crop': - new_w = opt.loadSize - new_h = opt.loadSize * h // w - - x = random.randint(0, np.maximum(0, new_w - opt.fineSize)) - y = random.randint(0, np.maximum(0, new_h - opt.fineSize)) - - flip = random.random() > 0.5 - return {'crop_pos': (x, y), 'flip': flip} - -def get_transform(opt, params, method=Image.BICUBIC, normalize=True): - transform_list = [] - if 'resize' in opt.resize_or_crop: - osize = [opt.loadSize, opt.loadSize] - transform_list.append(transforms.Scale(osize, method)) - elif 'scale_width' in opt.resize_or_crop: - transform_list.append(transforms.Lambda(lambda img: __scale_width(img, opt.loadSize, method))) - - if 'crop' in opt.resize_or_crop: - transform_list.append(transforms.Lambda(lambda img: __crop(img, params['crop_pos'], opt.fineSize))) - - if opt.resize_or_crop == 'none': - base = float(2 ** opt.n_downsample_global) - if opt.netG == 'local': - base *= (2 ** opt.n_local_enhancers) - transform_list.append(transforms.Lambda(lambda img: __make_power_2(img, base, method))) - - if opt.isTrain and not opt.no_flip: - transform_list.append(transforms.Lambda(lambda img: __flip(img, params['flip']))) - - transform_list += [transforms.ToTensor()] - - if normalize: - transform_list += [transforms.Normalize((0.5, 0.5, 0.5), - (0.5, 0.5, 0.5))] - return transforms.Compose(transform_list) - -def normalize(): - return transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) - -def __make_power_2(img, base, method=Image.BICUBIC): - ow, oh = img.size - h = int(round(oh / base) * base) - w = int(round(ow / base) * base) - if (h == oh) and (w == ow): - return img - return img.resize((w, h), method) - -def __scale_width(img, target_width, method=Image.BICUBIC): - ow, oh = img.size - if (ow == target_width): - return img - w = target_width - h = int(target_width * oh / ow) - return img.resize((w, h), method) - -def __crop(img, pos, size): - ow, oh = img.size - x1, y1 = pos - tw = th = size - if (ow > tw or oh > th): - return img.crop((x1, y1, x1 + tw, y1 + th)) - return img - -def __flip(img, flip): - if flip: - return img.transpose(Image.FLIP_LEFT_RIGHT) - return img diff --git a/pix2pixHD_attack/data/custom_dataset_data_loader.py b/pix2pixHD_attack/data/custom_dataset_data_loader.py deleted file mode 100755 index 0b98254..0000000 --- a/pix2pixHD_attack/data/custom_dataset_data_loader.py +++ /dev/null @@ -1,31 +0,0 @@ -import torch.utils.data -from data.base_data_loader import BaseDataLoader - - -def CreateDataset(opt): - dataset = None - from data.aligned_dataset import AlignedDataset - dataset = AlignedDataset() - - print("dataset [%s] was created" % (dataset.name())) - dataset.initialize(opt) - return dataset - -class CustomDatasetDataLoader(BaseDataLoader): - def name(self): - return 'CustomDatasetDataLoader' - - def initialize(self, opt): - BaseDataLoader.initialize(self, opt) - self.dataset = CreateDataset(opt) - self.dataloader = torch.utils.data.DataLoader( - self.dataset, - batch_size=opt.batchSize, - shuffle=not opt.serial_batches, - num_workers=int(opt.nThreads)) - - def load_data(self): - return self.dataloader - - def __len__(self): - return min(len(self.dataset), self.opt.max_dataset_size) diff --git a/pix2pixHD_attack/data/data_loader.py b/pix2pixHD_attack/data/data_loader.py deleted file mode 100755 index 2a4433a..0000000 --- a/pix2pixHD_attack/data/data_loader.py +++ /dev/null @@ -1,7 +0,0 @@ - -def CreateDataLoader(opt): - from data.custom_dataset_data_loader import CustomDatasetDataLoader - data_loader = CustomDatasetDataLoader() - print(data_loader.name()) - data_loader.initialize(opt) - return data_loader diff --git a/pix2pixHD_attack/data/image_folder.py b/pix2pixHD_attack/data/image_folder.py deleted file mode 100755 index df0141f..0000000 --- a/pix2pixHD_attack/data/image_folder.py +++ /dev/null @@ -1,65 +0,0 @@ -############################################################################### -# Code from -# https://github.com/pytorch/vision/blob/master/torchvision/datasets/folder.py -# Modified the original code so that it also loads images from the current -# directory as well as the subdirectories -############################################################################### -import torch.utils.data as data -from PIL import Image -import os - -IMG_EXTENSIONS = [ - '.jpg', '.JPG', '.jpeg', '.JPEG', - '.png', '.PNG', '.ppm', '.PPM', '.bmp', '.BMP', '.tiff' -] - - -def is_image_file(filename): - return any(filename.endswith(extension) for extension in IMG_EXTENSIONS) - - -def make_dataset(dir): - images = [] - assert os.path.isdir(dir), '%s is not a valid directory' % dir - - for root, _, fnames in sorted(os.walk(dir)): - for fname in fnames: - if is_image_file(fname): - path = os.path.join(root, fname) - images.append(path) - - return images - - -def default_loader(path): - return Image.open(path).convert('RGB') - - -class ImageFolder(data.Dataset): - - def __init__(self, root, transform=None, return_paths=False, - loader=default_loader): - imgs = make_dataset(root) - if len(imgs) == 0: - raise(RuntimeError("Found 0 images in: " + root + "\n" - "Supported image extensions are: " + - ",".join(IMG_EXTENSIONS))) - - self.root = root - self.imgs = imgs - self.transform = transform - self.return_paths = return_paths - self.loader = loader - - def __getitem__(self, index): - path = self.imgs[index] - img = self.loader(path) - if self.transform is not None: - img = self.transform(img) - if self.return_paths: - return img, path - else: - return img - - def __len__(self): - return len(self.imgs) diff --git a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000000_000576_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000000_000576_gtFine_instanceIds.png deleted file mode 100755 index 01da7ed..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000000_000576_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000000_001236_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000000_001236_gtFine_instanceIds.png deleted file mode 100755 index 75506bc..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000000_001236_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000000_003357_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000000_003357_gtFine_instanceIds.png deleted file mode 100755 index 9bd27b0..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000000_003357_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000000_011810_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000000_011810_gtFine_instanceIds.png deleted file mode 100755 index df84eee..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000000_011810_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000000_012868_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000000_012868_gtFine_instanceIds.png deleted file mode 100755 index ba1f7aa..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000000_012868_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_013710_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_013710_gtFine_instanceIds.png deleted file mode 100755 index d05b7db..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_013710_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_015328_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_015328_gtFine_instanceIds.png deleted file mode 100755 index 32d62a3..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_015328_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_023769_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_023769_gtFine_instanceIds.png deleted file mode 100755 index 9eef682..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_023769_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_028335_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_028335_gtFine_instanceIds.png deleted file mode 100755 index b1909d5..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_028335_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_032711_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_032711_gtFine_instanceIds.png deleted file mode 100755 index ac2e293..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_032711_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_033655_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_033655_gtFine_instanceIds.png deleted file mode 100755 index de7328e..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_033655_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_042733_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_042733_gtFine_instanceIds.png deleted file mode 100755 index a98d096..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_042733_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_047552_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_047552_gtFine_instanceIds.png deleted file mode 100755 index ab569e3..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_047552_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_054640_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_054640_gtFine_instanceIds.png deleted file mode 100755 index 5f246a6..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_054640_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_055387_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_055387_gtFine_instanceIds.png deleted file mode 100755 index 2e7d01f..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_inst/frankfurt_000001_055387_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000000_000576_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000000_000576_gtFine_labelIds.png deleted file mode 100755 index 8c9464c..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000000_000576_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000000_001236_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000000_001236_gtFine_labelIds.png deleted file mode 100755 index 9f0ca9f..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000000_001236_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000000_003357_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000000_003357_gtFine_labelIds.png deleted file mode 100755 index 1035e55..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000000_003357_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000000_011810_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000000_011810_gtFine_labelIds.png deleted file mode 100755 index a86913b..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000000_011810_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000000_012868_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000000_012868_gtFine_labelIds.png deleted file mode 100755 index fe81c83..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000000_012868_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_013710_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_013710_gtFine_labelIds.png deleted file mode 100755 index 72b4be4..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_013710_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_015328_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_015328_gtFine_labelIds.png deleted file mode 100755 index afefb6b..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_015328_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_023769_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_023769_gtFine_labelIds.png deleted file mode 100755 index f3af9df..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_023769_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_028335_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_028335_gtFine_labelIds.png deleted file mode 100755 index 5e65e3e..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_028335_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_032711_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_032711_gtFine_labelIds.png deleted file mode 100755 index ba07b73..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_032711_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_033655_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_033655_gtFine_labelIds.png deleted file mode 100755 index 77f519c..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_033655_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_042733_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_042733_gtFine_labelIds.png deleted file mode 100755 index ba08f1d..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_042733_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_047552_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_047552_gtFine_labelIds.png deleted file mode 100755 index 5dff09a..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_047552_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_054640_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_054640_gtFine_labelIds.png deleted file mode 100755 index cb2ab2b..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_054640_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_055387_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_055387_gtFine_labelIds.png deleted file mode 100755 index b00ef7e..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/test_label/frankfurt_000001_055387_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000000_000019_leftImg8bit.png b/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000000_000019_leftImg8bit.png deleted file mode 100755 index 0e6867e..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000000_000019_leftImg8bit.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000001_000019_leftImg8bit.png b/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000001_000019_leftImg8bit.png deleted file mode 100755 index d5a96ce..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000001_000019_leftImg8bit.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000002_000019_leftImg8bit.png b/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000002_000019_leftImg8bit.png deleted file mode 100755 index 10ce563..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000002_000019_leftImg8bit.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000003_000019_leftImg8bit.png b/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000003_000019_leftImg8bit.png deleted file mode 100755 index 3027fe1..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000003_000019_leftImg8bit.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000004_000019_leftImg8bit.png b/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000004_000019_leftImg8bit.png deleted file mode 100755 index 26945fc..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000004_000019_leftImg8bit.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000005_000019_leftImg8bit.png b/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000005_000019_leftImg8bit.png deleted file mode 100755 index 71fb41f..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000005_000019_leftImg8bit.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000006_000019_leftImg8bit.png b/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000006_000019_leftImg8bit.png deleted file mode 100755 index dd5cf30..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000006_000019_leftImg8bit.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000007_000019_leftImg8bit.png b/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000007_000019_leftImg8bit.png deleted file mode 100755 index 65653d7..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_img/aachen_000007_000019_leftImg8bit.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000000_000019_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000000_000019_gtFine_instanceIds.png deleted file mode 100755 index f4ee222..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000000_000019_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000001_000019_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000001_000019_gtFine_instanceIds.png deleted file mode 100755 index dd69137..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000001_000019_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000002_000019_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000002_000019_gtFine_instanceIds.png deleted file mode 100755 index bdad5e3..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000002_000019_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000003_000019_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000003_000019_gtFine_instanceIds.png deleted file mode 100755 index 91a035b..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000003_000019_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000004_000019_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000004_000019_gtFine_instanceIds.png deleted file mode 100755 index 0f5fc70..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000004_000019_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000005_000019_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000005_000019_gtFine_instanceIds.png deleted file mode 100755 index ac54ee9..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000005_000019_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000006_000019_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000006_000019_gtFine_instanceIds.png deleted file mode 100755 index 49f435f..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000006_000019_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000007_000019_gtFine_instanceIds.png b/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000007_000019_gtFine_instanceIds.png deleted file mode 100755 index 7c5ca0a..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_inst/aachen_000007_000019_gtFine_instanceIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000000_000019_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000000_000019_gtFine_labelIds.png deleted file mode 100755 index eed7ee6..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000000_000019_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000001_000019_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000001_000019_gtFine_labelIds.png deleted file mode 100755 index e9c25ee..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000001_000019_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000002_000019_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000002_000019_gtFine_labelIds.png deleted file mode 100755 index c96ab17..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000002_000019_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000003_000019_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000003_000019_gtFine_labelIds.png deleted file mode 100755 index da05594..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000003_000019_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000004_000019_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000004_000019_gtFine_labelIds.png deleted file mode 100755 index bb30bd9..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000004_000019_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000005_000019_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000005_000019_gtFine_labelIds.png deleted file mode 100755 index 958fc40..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000005_000019_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000006_000019_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000006_000019_gtFine_labelIds.png deleted file mode 100755 index 89f042e..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000006_000019_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000007_000019_gtFine_labelIds.png b/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000007_000019_gtFine_labelIds.png deleted file mode 100755 index 85b6922..0000000 Binary files a/pix2pixHD_attack/datasets/cityscapes/train_label/aachen_000007_000019_gtFine_labelIds.png and /dev/null differ diff --git a/pix2pixHD_attack/encode_features.py b/pix2pixHD_attack/encode_features.py deleted file mode 100755 index 158c85a..0000000 --- a/pix2pixHD_attack/encode_features.py +++ /dev/null @@ -1,54 +0,0 @@ -from options.train_options import TrainOptions -from data.data_loader import CreateDataLoader -from models.models import create_model -import numpy as np -import os - -opt = TrainOptions().parse() -opt.nThreads = 1 -opt.batchSize = 1 -opt.serial_batches = True -opt.no_flip = True -opt.instance_feat = True -opt.continue_train = True - -name = 'features' -save_path = os.path.join(opt.checkpoints_dir, opt.name) - -############ Initialize ######### -data_loader = CreateDataLoader(opt) -dataset = data_loader.load_data() -dataset_size = len(data_loader) -model = create_model(opt) - -########### Encode features ########### -reencode = True -if reencode: - features = {} - for label in range(opt.label_nc): - features[label] = np.zeros((0, opt.feat_num+1)) - for i, data in enumerate(dataset): - feat = model.module.encode_features(data['image'], data['inst']) - for label in range(opt.label_nc): - features[label] = np.append(features[label], feat[label], axis=0) - - print('%d / %d images' % (i+1, dataset_size)) - save_name = os.path.join(save_path, name + '.npy') - np.save(save_name, features) - -############## Clustering ########### -n_clusters = opt.n_clusters -load_name = os.path.join(save_path, name + '.npy') -features = np.load(load_name).item() -from sklearn.cluster import KMeans -centers = {} -for label in range(opt.label_nc): - feat = features[label] - feat = feat[feat[:,-1] > 0.5, :-1] - if feat.shape[0]: - n_clusters = min(feat.shape[0], opt.n_clusters) - kmeans = KMeans(n_clusters=n_clusters, random_state=0).fit(feat) - centers[label] = kmeans.cluster_centers_ -save_name = os.path.join(save_path, name + '_clustered_%03d.npy' % opt.n_clusters) -np.save(save_name, centers) -print('saving to %s' % save_name) \ No newline at end of file diff --git a/pix2pixHD_attack/models/__init__.py b/pix2pixHD_attack/models/__init__.py deleted file mode 100755 index e69de29..0000000 diff --git a/pix2pixHD_attack/models/base_model.py b/pix2pixHD_attack/models/base_model.py deleted file mode 100755 index f3f6b53..0000000 --- a/pix2pixHD_attack/models/base_model.py +++ /dev/null @@ -1,91 +0,0 @@ -import os -import torch -import sys - -class BaseModel(torch.nn.Module): - def name(self): - return 'BaseModel' - - def initialize(self, opt): - self.opt = opt - self.gpu_ids = opt.gpu_ids - self.isTrain = opt.isTrain - self.Tensor = torch.cuda.FloatTensor if self.gpu_ids else torch.Tensor - self.save_dir = os.path.join(opt.checkpoints_dir, opt.name) - - def set_input(self, input): - self.input = input - - def forward(self): - pass - - # used in test time, no backprop - def test(self): - pass - - def get_image_paths(self): - pass - - def optimize_parameters(self): - pass - - def get_current_visuals(self): - return self.input - - def get_current_errors(self): - return {} - - def save(self, label): - pass - - # helper saving function that can be used by subclasses - def save_network(self, network, network_label, epoch_label, gpu_ids): - save_filename = '%s_net_%s.pth' % (epoch_label, network_label) - save_path = os.path.join(self.save_dir, save_filename) - torch.save(network.cpu().state_dict(), save_path) - if len(gpu_ids) and torch.cuda.is_available(): - network.cuda() - - # helper loading function that can be used by subclasses - def load_network(self, network, network_label, epoch_label, save_dir=''): - save_filename = '%s_net_%s.pth' % (epoch_label, network_label) - if not save_dir: - save_dir = self.save_dir - save_path = os.path.join(save_dir, save_filename) - if not os.path.isfile(save_path): - print('%s not exists yet!' % save_path) - if network_label == 'G': - raise('Generator must exist!') - else: - #network.load_state_dict(torch.load(save_path)) - try: - network.load_state_dict(torch.load(save_path)) - except: - pretrained_dict = torch.load(save_path) - model_dict = network.state_dict() - try: - pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict} - network.load_state_dict(pretrained_dict) - if self.opt.verbose: - print('Pretrained network %s has excessive layers; Only loading layers that are used' % network_label) - except: - print('Pretrained network %s has fewer layers; The following are not initialized:' % network_label) - for k, v in pretrained_dict.items(): - if v.size() == model_dict[k].size(): - model_dict[k] = v - - if sys.version_info >= (3,0): - not_initialized = set() - else: - from sets import Set - not_initialized = Set() - - for k, v in model_dict.items(): - if k not in pretrained_dict or v.size() != pretrained_dict[k].size(): - not_initialized.add(k.split('.')[0]) - - print(sorted(not_initialized)) - network.load_state_dict(model_dict) - - def update_learning_rate(): - pass diff --git a/pix2pixHD_attack/models/models.py b/pix2pixHD_attack/models/models.py deleted file mode 100755 index be1e30e..0000000 --- a/pix2pixHD_attack/models/models.py +++ /dev/null @@ -1,20 +0,0 @@ -import torch - -def create_model(opt): - if opt.model == 'pix2pixHD': - from .pix2pixHD_model import Pix2PixHDModel, InferenceModel - if opt.isTrain: - model = Pix2PixHDModel() - else: - model = InferenceModel() - else: - from .ui_model import UIModel - model = UIModel() - model.initialize(opt) - if opt.verbose: - print("model [%s] was created" % (model.name())) - - if opt.isTrain and len(opt.gpu_ids) and not opt.fp16: - model = torch.nn.DataParallel(model, device_ids=opt.gpu_ids) - - return model diff --git a/pix2pixHD_attack/models/networks.py b/pix2pixHD_attack/models/networks.py deleted file mode 100755 index ee05d85..0000000 --- a/pix2pixHD_attack/models/networks.py +++ /dev/null @@ -1,416 +0,0 @@ -import torch -import torch.nn as nn -import functools -from torch.autograd import Variable -import numpy as np - -############################################################################### -# Functions -############################################################################### -def weights_init(m): - classname = m.__class__.__name__ - if classname.find('Conv') != -1: - m.weight.data.normal_(0.0, 0.02) - elif classname.find('BatchNorm2d') != -1: - m.weight.data.normal_(1.0, 0.02) - m.bias.data.fill_(0) - -def get_norm_layer(norm_type='instance'): - if norm_type == 'batch': - norm_layer = functools.partial(nn.BatchNorm2d, affine=True) - elif norm_type == 'instance': - norm_layer = functools.partial(nn.InstanceNorm2d, affine=False) - else: - raise NotImplementedError('normalization layer [%s] is not found' % norm_type) - return norm_layer - -def define_G(input_nc, output_nc, ngf, netG, n_downsample_global=3, n_blocks_global=9, n_local_enhancers=1, - n_blocks_local=3, norm='instance', gpu_ids=[]): - norm_layer = get_norm_layer(norm_type=norm) - if netG == 'global': - netG = GlobalGenerator(input_nc, output_nc, ngf, n_downsample_global, n_blocks_global, norm_layer) - elif netG == 'local': - netG = LocalEnhancer(input_nc, output_nc, ngf, n_downsample_global, n_blocks_global, - n_local_enhancers, n_blocks_local, norm_layer) - elif netG == 'encoder': - netG = Encoder(input_nc, output_nc, ngf, n_downsample_global, norm_layer) - else: - raise('generator not implemented!') - print(netG) - if len(gpu_ids) > 0: - assert(torch.cuda.is_available()) - netG.cuda(gpu_ids[0]) - netG.apply(weights_init) - return netG - -def define_D(input_nc, ndf, n_layers_D, norm='instance', use_sigmoid=False, num_D=1, getIntermFeat=False, gpu_ids=[]): - norm_layer = get_norm_layer(norm_type=norm) - netD = MultiscaleDiscriminator(input_nc, ndf, n_layers_D, norm_layer, use_sigmoid, num_D, getIntermFeat) - print(netD) - if len(gpu_ids) > 0: - assert(torch.cuda.is_available()) - netD.cuda(gpu_ids[0]) - netD.apply(weights_init) - return netD - -def print_network(net): - if isinstance(net, list): - net = net[0] - num_params = 0 - for param in net.parameters(): - num_params += param.numel() - print(net) - print('Total number of parameters: %d' % num_params) - -############################################################################## -# Losses -############################################################################## -class GANLoss(nn.Module): - def __init__(self, use_lsgan=True, target_real_label=1.0, target_fake_label=0.0, - tensor=torch.FloatTensor): - super(GANLoss, self).__init__() - self.real_label = target_real_label - self.fake_label = target_fake_label - self.real_label_var = None - self.fake_label_var = None - self.Tensor = tensor - if use_lsgan: - self.loss = nn.MSELoss() - else: - self.loss = nn.BCELoss() - - def get_target_tensor(self, input, target_is_real): - target_tensor = None - if target_is_real: - create_label = ((self.real_label_var is None) or - (self.real_label_var.numel() != input.numel())) - if create_label: - real_tensor = self.Tensor(input.size()).fill_(self.real_label) - self.real_label_var = Variable(real_tensor, requires_grad=False) - target_tensor = self.real_label_var - else: - create_label = ((self.fake_label_var is None) or - (self.fake_label_var.numel() != input.numel())) - if create_label: - fake_tensor = self.Tensor(input.size()).fill_(self.fake_label) - self.fake_label_var = Variable(fake_tensor, requires_grad=False) - target_tensor = self.fake_label_var - return target_tensor - - def __call__(self, input, target_is_real): - if isinstance(input[0], list): - loss = 0 - for input_i in input: - pred = input_i[-1] - target_tensor = self.get_target_tensor(pred, target_is_real) - loss += self.loss(pred, target_tensor) - return loss - else: - target_tensor = self.get_target_tensor(input[-1], target_is_real) - return self.loss(input[-1], target_tensor) - -class VGGLoss(nn.Module): - def __init__(self, gpu_ids): - super(VGGLoss, self).__init__() - self.vgg = Vgg19().cuda() - self.criterion = nn.L1Loss() - self.weights = [1.0/32, 1.0/16, 1.0/8, 1.0/4, 1.0] - - def forward(self, x, y): - x_vgg, y_vgg = self.vgg(x), self.vgg(y) - loss = 0 - for i in range(len(x_vgg)): - loss += self.weights[i] * self.criterion(x_vgg[i], y_vgg[i].detach()) - return loss - -############################################################################## -# Generator -############################################################################## -class LocalEnhancer(nn.Module): - def __init__(self, input_nc, output_nc, ngf=32, n_downsample_global=3, n_blocks_global=9, - n_local_enhancers=1, n_blocks_local=3, norm_layer=nn.BatchNorm2d, padding_type='reflect'): - super(LocalEnhancer, self).__init__() - self.n_local_enhancers = n_local_enhancers - - ###### global generator model ##### - ngf_global = ngf * (2**n_local_enhancers) - model_global = GlobalGenerator(input_nc, output_nc, ngf_global, n_downsample_global, n_blocks_global, norm_layer).model - model_global = [model_global[i] for i in range(len(model_global)-3)] # get rid of final convolution layers - self.model = nn.Sequential(*model_global) - - ###### local enhancer layers ##### - for n in range(1, n_local_enhancers+1): - ### downsample - ngf_global = ngf * (2**(n_local_enhancers-n)) - model_downsample = [nn.ReflectionPad2d(3), nn.Conv2d(input_nc, ngf_global, kernel_size=7, padding=0), - norm_layer(ngf_global), nn.ReLU(True), - nn.Conv2d(ngf_global, ngf_global * 2, kernel_size=3, stride=2, padding=1), - norm_layer(ngf_global * 2), nn.ReLU(True)] - ### residual blocks - model_upsample = [] - for i in range(n_blocks_local): - model_upsample += [ResnetBlock(ngf_global * 2, padding_type=padding_type, norm_layer=norm_layer)] - - ### upsample - model_upsample += [nn.ConvTranspose2d(ngf_global * 2, ngf_global, kernel_size=3, stride=2, padding=1, output_padding=1), - norm_layer(ngf_global), nn.ReLU(True)] - - ### final convolution - if n == n_local_enhancers: - model_upsample += [nn.ReflectionPad2d(3), nn.Conv2d(ngf, output_nc, kernel_size=7, padding=0), nn.Tanh()] - - setattr(self, 'model'+str(n)+'_1', nn.Sequential(*model_downsample)) - setattr(self, 'model'+str(n)+'_2', nn.Sequential(*model_upsample)) - - self.downsample = nn.AvgPool2d(3, stride=2, padding=[1, 1], count_include_pad=False) - - def forward(self, input): - ### create input pyramid - input_downsampled = [input] - for i in range(self.n_local_enhancers): - input_downsampled.append(self.downsample(input_downsampled[-1])) - - ### output at coarest level - output_prev = self.model(input_downsampled[-1]) - ### build up one layer at a time - for n_local_enhancers in range(1, self.n_local_enhancers+1): - model_downsample = getattr(self, 'model'+str(n_local_enhancers)+'_1') - model_upsample = getattr(self, 'model'+str(n_local_enhancers)+'_2') - input_i = input_downsampled[self.n_local_enhancers-n_local_enhancers] - output_prev = model_upsample(model_downsample(input_i) + output_prev) - return output_prev - -class GlobalGenerator(nn.Module): - def __init__(self, input_nc, output_nc, ngf=64, n_downsampling=3, n_blocks=9, norm_layer=nn.BatchNorm2d, - padding_type='reflect'): - assert(n_blocks >= 0) - super(GlobalGenerator, self).__init__() - activation = nn.ReLU(True) - - model = [nn.ReflectionPad2d(3), nn.Conv2d(input_nc, ngf, kernel_size=7, padding=0), norm_layer(ngf), activation] - ### downsample - for i in range(n_downsampling): - mult = 2**i - model += [nn.Conv2d(ngf * mult, ngf * mult * 2, kernel_size=3, stride=2, padding=1), - norm_layer(ngf * mult * 2), activation] - - ### resnet blocks - mult = 2**n_downsampling - for i in range(n_blocks): - model += [ResnetBlock(ngf * mult, padding_type=padding_type, activation=activation, norm_layer=norm_layer)] - - ### upsample - for i in range(n_downsampling): - mult = 2**(n_downsampling - i) - model += [nn.ConvTranspose2d(ngf * mult, int(ngf * mult / 2), kernel_size=3, stride=2, padding=1, output_padding=1), - norm_layer(int(ngf * mult / 2)), activation] - model += [nn.ReflectionPad2d(3), nn.Conv2d(ngf, output_nc, kernel_size=7, padding=0), nn.Tanh()] - self.model = nn.Sequential(*model) - - def forward(self, input): - return self.model(input) - -# Define a resnet block -class ResnetBlock(nn.Module): - def __init__(self, dim, padding_type, norm_layer, activation=nn.ReLU(True), use_dropout=False): - super(ResnetBlock, self).__init__() - self.conv_block = self.build_conv_block(dim, padding_type, norm_layer, activation, use_dropout) - - def build_conv_block(self, dim, padding_type, norm_layer, activation, use_dropout): - conv_block = [] - p = 0 - if padding_type == 'reflect': - conv_block += [nn.ReflectionPad2d(1)] - elif padding_type == 'replicate': - conv_block += [nn.ReplicationPad2d(1)] - elif padding_type == 'zero': - p = 1 - else: - raise NotImplementedError('padding [%s] is not implemented' % padding_type) - - conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=p), - norm_layer(dim), - activation] - if use_dropout: - conv_block += [nn.Dropout(0.5)] - - p = 0 - if padding_type == 'reflect': - conv_block += [nn.ReflectionPad2d(1)] - elif padding_type == 'replicate': - conv_block += [nn.ReplicationPad2d(1)] - elif padding_type == 'zero': - p = 1 - else: - raise NotImplementedError('padding [%s] is not implemented' % padding_type) - conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=p), - norm_layer(dim)] - - return nn.Sequential(*conv_block) - - def forward(self, x): - out = x + self.conv_block(x) - return out - -class Encoder(nn.Module): - def __init__(self, input_nc, output_nc, ngf=32, n_downsampling=4, norm_layer=nn.BatchNorm2d): - super(Encoder, self).__init__() - self.output_nc = output_nc - - model = [nn.ReflectionPad2d(3), nn.Conv2d(input_nc, ngf, kernel_size=7, padding=0), - norm_layer(ngf), nn.ReLU(True)] - ### downsample - for i in range(n_downsampling): - mult = 2**i - model += [nn.Conv2d(ngf * mult, ngf * mult * 2, kernel_size=3, stride=2, padding=1), - norm_layer(ngf * mult * 2), nn.ReLU(True)] - - ### upsample - for i in range(n_downsampling): - mult = 2**(n_downsampling - i) - model += [nn.ConvTranspose2d(ngf * mult, int(ngf * mult / 2), kernel_size=3, stride=2, padding=1, output_padding=1), - norm_layer(int(ngf * mult / 2)), nn.ReLU(True)] - - model += [nn.ReflectionPad2d(3), nn.Conv2d(ngf, output_nc, kernel_size=7, padding=0), nn.Tanh()] - self.model = nn.Sequential(*model) - - def forward(self, input, inst): - outputs = self.model(input) - - # instance-wise average pooling - outputs_mean = outputs.clone() - inst_list = np.unique(inst.cpu().numpy().astype(int)) - for i in inst_list: - for b in range(input.size()[0]): - indices = (inst[b:b+1] == int(i)).nonzero() # n x 4 - for j in range(self.output_nc): - output_ins = outputs[indices[:,0] + b, indices[:,1] + j, indices[:,2], indices[:,3]] - mean_feat = torch.mean(output_ins).expand_as(output_ins) - outputs_mean[indices[:,0] + b, indices[:,1] + j, indices[:,2], indices[:,3]] = mean_feat - return outputs_mean - -class MultiscaleDiscriminator(nn.Module): - def __init__(self, input_nc, ndf=64, n_layers=3, norm_layer=nn.BatchNorm2d, - use_sigmoid=False, num_D=3, getIntermFeat=False): - super(MultiscaleDiscriminator, self).__init__() - self.num_D = num_D - self.n_layers = n_layers - self.getIntermFeat = getIntermFeat - - for i in range(num_D): - netD = NLayerDiscriminator(input_nc, ndf, n_layers, norm_layer, use_sigmoid, getIntermFeat) - if getIntermFeat: - for j in range(n_layers+2): - setattr(self, 'scale'+str(i)+'_layer'+str(j), getattr(netD, 'model'+str(j))) - else: - setattr(self, 'layer'+str(i), netD.model) - - self.downsample = nn.AvgPool2d(3, stride=2, padding=[1, 1], count_include_pad=False) - - def singleD_forward(self, model, input): - if self.getIntermFeat: - result = [input] - for i in range(len(model)): - result.append(model[i](result[-1])) - return result[1:] - else: - return [model(input)] - - def forward(self, input): - num_D = self.num_D - result = [] - input_downsampled = input - for i in range(num_D): - if self.getIntermFeat: - model = [getattr(self, 'scale'+str(num_D-1-i)+'_layer'+str(j)) for j in range(self.n_layers+2)] - else: - model = getattr(self, 'layer'+str(num_D-1-i)) - result.append(self.singleD_forward(model, input_downsampled)) - if i != (num_D-1): - input_downsampled = self.downsample(input_downsampled) - return result - -# Defines the PatchGAN discriminator with the specified arguments. -class NLayerDiscriminator(nn.Module): - def __init__(self, input_nc, ndf=64, n_layers=3, norm_layer=nn.BatchNorm2d, use_sigmoid=False, getIntermFeat=False): - super(NLayerDiscriminator, self).__init__() - self.getIntermFeat = getIntermFeat - self.n_layers = n_layers - - kw = 4 - padw = int(np.ceil((kw-1.0)/2)) - sequence = [[nn.Conv2d(input_nc, ndf, kernel_size=kw, stride=2, padding=padw), nn.LeakyReLU(0.2, True)]] - - nf = ndf - for n in range(1, n_layers): - nf_prev = nf - nf = min(nf * 2, 512) - sequence += [[ - nn.Conv2d(nf_prev, nf, kernel_size=kw, stride=2, padding=padw), - norm_layer(nf), nn.LeakyReLU(0.2, True) - ]] - - nf_prev = nf - nf = min(nf * 2, 512) - sequence += [[ - nn.Conv2d(nf_prev, nf, kernel_size=kw, stride=1, padding=padw), - norm_layer(nf), - nn.LeakyReLU(0.2, True) - ]] - - sequence += [[nn.Conv2d(nf, 1, kernel_size=kw, stride=1, padding=padw)]] - - if use_sigmoid: - sequence += [[nn.Sigmoid()]] - - if getIntermFeat: - for n in range(len(sequence)): - setattr(self, 'model'+str(n), nn.Sequential(*sequence[n])) - else: - sequence_stream = [] - for n in range(len(sequence)): - sequence_stream += sequence[n] - self.model = nn.Sequential(*sequence_stream) - - def forward(self, input): - if self.getIntermFeat: - res = [input] - for n in range(self.n_layers+2): - model = getattr(self, 'model'+str(n)) - res.append(model(res[-1])) - return res[1:] - else: - return self.model(input) - -from torchvision import models -class Vgg19(torch.nn.Module): - def __init__(self, requires_grad=False): - super(Vgg19, self).__init__() - vgg_pretrained_features = models.vgg19(pretrained=True).features - self.slice1 = torch.nn.Sequential() - self.slice2 = torch.nn.Sequential() - self.slice3 = torch.nn.Sequential() - self.slice4 = torch.nn.Sequential() - self.slice5 = torch.nn.Sequential() - for x in range(2): - self.slice1.add_module(str(x), vgg_pretrained_features[x]) - for x in range(2, 7): - self.slice2.add_module(str(x), vgg_pretrained_features[x]) - for x in range(7, 12): - self.slice3.add_module(str(x), vgg_pretrained_features[x]) - for x in range(12, 21): - self.slice4.add_module(str(x), vgg_pretrained_features[x]) - for x in range(21, 30): - self.slice5.add_module(str(x), vgg_pretrained_features[x]) - if not requires_grad: - for param in self.parameters(): - param.requires_grad = False - - def forward(self, X): - h_relu1 = self.slice1(X) - h_relu2 = self.slice2(h_relu1) - h_relu3 = self.slice3(h_relu2) - h_relu4 = self.slice4(h_relu3) - h_relu5 = self.slice5(h_relu4) - out = [h_relu1, h_relu2, h_relu3, h_relu4, h_relu5] - return out diff --git a/pix2pixHD_attack/models/pix2pixHD_model.py b/pix2pixHD_attack/models/pix2pixHD_model.py deleted file mode 100755 index a0c6b57..0000000 --- a/pix2pixHD_attack/models/pix2pixHD_model.py +++ /dev/null @@ -1,363 +0,0 @@ -import numpy as np -import torch -import os -from torch.autograd import Variable -from util.image_pool import ImagePool -from .base_model import BaseModel -from . import networks -from util import attacks - -class Pix2PixHDModel(BaseModel): - def name(self): - return 'Pix2PixHDModel' - - def init_loss_filter(self, use_gan_feat_loss, use_vgg_loss): - flags = (True, use_gan_feat_loss, use_vgg_loss, True, True) - def loss_filter(g_gan, g_gan_feat, g_vgg, d_real, d_fake): - return [l for (l,f) in zip((g_gan,g_gan_feat,g_vgg,d_real,d_fake),flags) if f] - return loss_filter - - def initialize(self, opt): - BaseModel.initialize(self, opt) - if opt.resize_or_crop != 'none' or not opt.isTrain: # when training at full res this causes OOM - torch.backends.cudnn.benchmark = True - self.isTrain = opt.isTrain - self.use_features = opt.instance_feat or opt.label_feat - self.gen_features = self.use_features and not self.opt.load_features - input_nc = opt.label_nc if opt.label_nc != 0 else opt.input_nc - - ##### define networks - # Generator network - netG_input_nc = input_nc - if not opt.no_instance: - netG_input_nc += 1 - if self.use_features: - netG_input_nc += opt.feat_num - self.netG = networks.define_G(netG_input_nc, opt.output_nc, opt.ngf, opt.netG, - opt.n_downsample_global, opt.n_blocks_global, opt.n_local_enhancers, - opt.n_blocks_local, opt.norm, gpu_ids=self.gpu_ids) - - # Discriminator network - if self.isTrain: - use_sigmoid = opt.no_lsgan - netD_input_nc = input_nc + opt.output_nc - if not opt.no_instance: - netD_input_nc += 1 - self.netD = networks.define_D(netD_input_nc, opt.ndf, opt.n_layers_D, opt.norm, use_sigmoid, - opt.num_D, not opt.no_ganFeat_loss, gpu_ids=self.gpu_ids) - - ### Encoder network - if self.gen_features: - self.netE = networks.define_G(opt.output_nc, opt.feat_num, opt.nef, 'encoder', - opt.n_downsample_E, norm=opt.norm, gpu_ids=self.gpu_ids) - if self.opt.verbose: - print('---------- Networks initialized -------------') - - # load networks - if not self.isTrain or opt.continue_train or opt.load_pretrain: - pretrained_path = '' if not self.isTrain else opt.load_pretrain - self.load_network(self.netG, 'G', opt.which_epoch, pretrained_path) - if self.isTrain: - self.load_network(self.netD, 'D', opt.which_epoch, pretrained_path) - if self.gen_features: - self.load_network(self.netE, 'E', opt.which_epoch, pretrained_path) - - # set loss functions and optimizers - if self.isTrain: - if opt.pool_size > 0 and (len(self.gpu_ids)) > 1: - raise NotImplementedError("Fake Pool Not Implemented for MultiGPU") - self.fake_pool = ImagePool(opt.pool_size) - self.old_lr = opt.lr - - # define loss functions - self.loss_filter = self.init_loss_filter(not opt.no_ganFeat_loss, not opt.no_vgg_loss) - - self.criterionGAN = networks.GANLoss(use_lsgan=not opt.no_lsgan, tensor=self.Tensor) - self.criterionFeat = torch.nn.L1Loss() - if not opt.no_vgg_loss: - self.criterionVGG = networks.VGGLoss(self.gpu_ids) - - - # Names so we can breakout loss - self.loss_names = self.loss_filter('G_GAN','G_GAN_Feat','G_VGG','D_real', 'D_fake') - - # initialize optimizers - # optimizer G - if opt.niter_fix_global > 0: - import sys - if sys.version_info >= (3,0): - finetune_list = set() - else: - from sets import Set - finetune_list = Set() - - params_dict = dict(self.netG.named_parameters()) - params = [] - for key, value in params_dict.items(): - if key.startswith('model' + str(opt.n_local_enhancers)): - params += [value] - finetune_list.add(key.split('.')[0]) - print('------------- Only training the local enhancer network (for %d epochs) ------------' % opt.niter_fix_global) - print('The layers that are finetuned are ', sorted(finetune_list)) - else: - params = list(self.netG.parameters()) - if self.gen_features: - params += list(self.netE.parameters()) - self.optimizer_G = torch.optim.Adam(params, lr=opt.lr, betas=(opt.beta1, 0.999)) - - # optimizer D - params = list(self.netD.parameters()) - self.optimizer_D = torch.optim.Adam(params, lr=opt.lr, betas=(opt.beta1, 0.999)) - - def encode_input(self, label_map, inst_map=None, real_image=None, feat_map=None, infer=False): - if self.opt.label_nc == 0: - input_label = label_map.data.cuda() - else: - # create one-hot vector for label map - size = label_map.size() - oneHot_size = (size[0], self.opt.label_nc, size[2], size[3]) - input_label = torch.cuda.FloatTensor(torch.Size(oneHot_size)).zero_() - input_label = input_label.scatter_(1, label_map.data.long().cuda(), 1.0) - if self.opt.data_type == 16: - input_label = input_label.half() - - # get edges from instance map - if not self.opt.no_instance: - inst_map = inst_map.data.cuda() - edge_map = self.get_edges(inst_map) - input_label = torch.cat((input_label, edge_map), dim=1) - input_label = Variable(input_label, volatile=infer) - - # real images for training - if real_image is not None: - real_image = Variable(real_image.data.cuda()) - - # instance map for feature encoding - if self.use_features: - # get precomputed feature maps - if self.opt.load_features: - feat_map = Variable(feat_map.data.cuda()) - if self.opt.label_feat: - inst_map = label_map.cuda() - - return input_label, inst_map, real_image, feat_map - - def discriminate(self, input_label, test_image, use_pool=False): - input_concat = torch.cat((input_label, test_image.detach()), dim=1) - if use_pool: - fake_query = self.fake_pool.query(input_concat) - return self.netD.forward(fake_query) - else: - return self.netD.forward(input_concat) - - def forward(self, label, inst, image, feat, infer=False): - # Encode Inputs - input_label, inst_map, real_image, feat_map = self.encode_input(label, inst, image, feat) - - # Fake Generation - if self.use_features: - if not self.opt.load_features: - feat_map = self.netE.forward(real_image, inst_map) - input_concat = torch.cat((input_label, feat_map), dim=1) - else: - input_concat = input_label - - fake_image = self.netG.forward(input_concat) - # fake_image = self.netG.forward(input_adv) - - # Fake Detection and Loss - pred_fake_pool = self.discriminate(input_label, fake_image, use_pool=True) - loss_D_fake = self.criterionGAN(pred_fake_pool, False) - - # Real Detection and Loss - pred_real = self.discriminate(input_label, real_image) - loss_D_real = self.criterionGAN(pred_real, True) - - # GAN loss (Fake Passability Loss) - pred_fake = self.netD.forward(torch.cat((input_label, fake_image), dim=1)) - loss_G_GAN = self.criterionGAN(pred_fake, True) - - # GAN feature matching loss - loss_G_GAN_Feat = 0 - if not self.opt.no_ganFeat_loss: - feat_weights = 4.0 / (self.opt.n_layers_D + 1) - D_weights = 1.0 / self.opt.num_D - for i in range(self.opt.num_D): - for j in range(len(pred_fake[i])-1): - loss_G_GAN_Feat += D_weights * feat_weights * \ - self.criterionFeat(pred_fake[i][j], pred_real[i][j].detach()) * self.opt.lambda_feat - - # VGG feature matching loss - loss_G_VGG = 0 - if not self.opt.no_vgg_loss: - loss_G_VGG = self.criterionVGG(fake_image, real_image) * self.opt.lambda_feat - - # Only return the fake_B image if necessary to save BW - return [ self.loss_filter( loss_G_GAN, loss_G_GAN_Feat, loss_G_VGG, loss_D_real, loss_D_fake ), None if not infer else fake_image ] - - def inference(self, label, inst, image=None): - # Encode Inputs - image = Variable(image) if image is not None else None - input_label, inst_map, real_image, _ = self.encode_input(Variable(label), Variable(inst), image, infer=True) - - # Fake Generation - if self.use_features: - if self.opt.use_encoded_image: - # encode the real image to get feature map - feat_map = self.netE.forward(real_image, inst_map) - else: - # sample clusters from precomputed features - feat_map = self.sample_features(inst_map) - input_concat = torch.cat((input_label, feat_map), dim=1) - else: - input_concat = input_label - - # Attack - # pgd_attack = attacks.LinfPGDAttack(model=self.netG) - # black = np.zeros((1, 3, input_concat.size(2), input_concat.size(3))) - # black = torch.FloatTensor(black).cuda() - # # print(input_concat.size()) - # input_adv, perturb = pgd_attack.perturb(input_concat, black) - - with torch.no_grad(): - fake_image = self.netG.forward(input_concat) - - return fake_image - - def inference_attack(self, label, inst, image=None, perturb=None): - # Encode Inputs - image = Variable(image) if image is not None else None - input_label, inst_map, real_image, _ = self.encode_input(Variable(label), Variable(inst), image, infer=True) - - # Fake Generation - if self.use_features: - if self.opt.use_encoded_image: - # encode the real image to get feature map - feat_map = self.netE.forward(real_image, inst_map) - else: - # sample clusters from precomputed features - feat_map = self.sample_features(inst_map) - input_concat = torch.cat((input_label, feat_map), dim=1) - else: - input_concat = input_label - - input_adv = torch.clamp(input_concat + perturb, min=-1, max=1) - - with torch.no_grad(): - fake_image = self.netG.forward(input_adv) - - return fake_image, input_adv - - def attack(self, label, inst, image=None): - # Encode Inputs - image = Variable(image) if image is not None else None - input_label, inst_map, real_image, _ = self.encode_input(Variable(label), Variable(inst), image, infer=True) - - # Fake Generation - if self.use_features: - if self.opt.use_encoded_image: - # encode the real image to get feature map - feat_map = self.netE.forward(real_image, inst_map) - else: - # sample clusters from precomputed features - feat_map = self.sample_features(inst_map) - input_concat = torch.cat((input_label, feat_map), dim=1) - else: - input_concat = input_label - - # Attack - pgd_attack = attacks.LinfPGDAttack(model=self.netG) - black = np.zeros((1, 3, input_concat.size(2), input_concat.size(3))) - black = torch.FloatTensor(black).cuda() - # print(input_concat.size()) - input_adv, perturb = pgd_attack.perturb(input_concat, black) - - return input_adv, perturb - - def sample_features(self, inst): - # read precomputed feature clusters - cluster_path = os.path.join(self.opt.checkpoints_dir, self.opt.name, self.opt.cluster_path) - features_clustered = np.load(cluster_path, encoding='latin1').item() - - # randomly sample from the feature clusters - inst_np = inst.cpu().numpy().astype(int) - feat_map = self.Tensor(inst.size()[0], self.opt.feat_num, inst.size()[2], inst.size()[3]) - for i in np.unique(inst_np): - label = i if i < 1000 else i//1000 - if label in features_clustered: - feat = features_clustered[label] - cluster_idx = np.random.randint(0, feat.shape[0]) - - idx = (inst == int(i)).nonzero() - for k in range(self.opt.feat_num): - feat_map[idx[:,0], idx[:,1] + k, idx[:,2], idx[:,3]] = feat[cluster_idx, k] - if self.opt.data_type==16: - feat_map = feat_map.half() - return feat_map - - def encode_features(self, image, inst): - image = Variable(image.cuda(), volatile=True) - feat_num = self.opt.feat_num - h, w = inst.size()[2], inst.size()[3] - block_num = 32 - feat_map = self.netE.forward(image, inst.cuda()) - inst_np = inst.cpu().numpy().astype(int) - feature = {} - for i in range(self.opt.label_nc): - feature[i] = np.zeros((0, feat_num+1)) - for i in np.unique(inst_np): - label = i if i < 1000 else i//1000 - idx = (inst == int(i)).nonzero() - num = idx.size()[0] - idx = idx[num//2,:] - val = np.zeros((1, feat_num+1)) - for k in range(feat_num): - val[0, k] = feat_map[idx[0], idx[1] + k, idx[2], idx[3]].data[0] - val[0, feat_num] = float(num) / (h * w // block_num) - feature[label] = np.append(feature[label], val, axis=0) - return feature - - def get_edges(self, t): - edge = torch.cuda.ByteTensor(t.size()).zero_() - edge = edge.bool() - edge[:,:,:,1:] = edge[:,:,:,1:] | (t[:,:,:,1:] != t[:,:,:,:-1]) - edge[:,:,:,:-1] = edge[:,:,:,:-1] | (t[:,:,:,1:] != t[:,:,:,:-1]) - edge[:,:,1:,:] = edge[:,:,1:,:] | (t[:,:,1:,:] != t[:,:,:-1,:]) - edge[:,:,:-1,:] = edge[:,:,:-1,:] | (t[:,:,1:,:] != t[:,:,:-1,:]) - if self.opt.data_type==16: - return edge.half() - else: - return edge.float() - - def save(self, which_epoch): - self.save_network(self.netG, 'G', which_epoch, self.gpu_ids) - self.save_network(self.netD, 'D', which_epoch, self.gpu_ids) - if self.gen_features: - self.save_network(self.netE, 'E', which_epoch, self.gpu_ids) - - def update_fixed_params(self): - # after fixing the global generator for a number of iterations, also start finetuning it - params = list(self.netG.parameters()) - if self.gen_features: - params += list(self.netE.parameters()) - self.optimizer_G = torch.optim.Adam(params, lr=self.opt.lr, betas=(self.opt.beta1, 0.999)) - if self.opt.verbose: - print('------------ Now also finetuning global generator -----------') - - def update_learning_rate(self): - lrd = self.opt.lr / self.opt.niter_decay - lr = self.old_lr - lrd - for param_group in self.optimizer_D.param_groups: - param_group['lr'] = lr - for param_group in self.optimizer_G.param_groups: - param_group['lr'] = lr - if self.opt.verbose: - print('update learning rate: %f -> %f' % (self.old_lr, lr)) - self.old_lr = lr - -class InferenceModel(Pix2PixHDModel): - def forward(self, inp): - label, inst = inp - return self.inference(label, inst) - - diff --git a/pix2pixHD_attack/models/ui_model.py b/pix2pixHD_attack/models/ui_model.py deleted file mode 100755 index c5b3433..0000000 --- a/pix2pixHD_attack/models/ui_model.py +++ /dev/null @@ -1,347 +0,0 @@ -import torch -from torch.autograd import Variable -from collections import OrderedDict -import numpy as np -import os -from PIL import Image -import util.util as util -from .base_model import BaseModel -from . import networks - -class UIModel(BaseModel): - def name(self): - return 'UIModel' - - def initialize(self, opt): - assert(not opt.isTrain) - BaseModel.initialize(self, opt) - self.use_features = opt.instance_feat or opt.label_feat - - netG_input_nc = opt.label_nc - if not opt.no_instance: - netG_input_nc += 1 - if self.use_features: - netG_input_nc += opt.feat_num - - self.netG = networks.define_G(netG_input_nc, opt.output_nc, opt.ngf, opt.netG, - opt.n_downsample_global, opt.n_blocks_global, opt.n_local_enhancers, - opt.n_blocks_local, opt.norm, gpu_ids=self.gpu_ids) - self.load_network(self.netG, 'G', opt.which_epoch) - - print('---------- Networks initialized -------------') - - def toTensor(self, img, normalize=False): - tensor = torch.from_numpy(np.array(img, np.int32, copy=False)) - tensor = tensor.view(1, img.size[1], img.size[0], len(img.mode)) - tensor = tensor.transpose(1, 2).transpose(1, 3).contiguous() - if normalize: - return (tensor.float()/255.0 - 0.5) / 0.5 - return tensor.float() - - def load_image(self, label_path, inst_path, feat_path): - opt = self.opt - # read label map - label_img = Image.open(label_path) - if label_path.find('face') != -1: - label_img = label_img.convert('L') - ow, oh = label_img.size - w = opt.loadSize - h = int(w * oh / ow) - label_img = label_img.resize((w, h), Image.NEAREST) - label_map = self.toTensor(label_img) - - # onehot vector input for label map - self.label_map = label_map.cuda() - oneHot_size = (1, opt.label_nc, h, w) - input_label = self.Tensor(torch.Size(oneHot_size)).zero_() - self.input_label = input_label.scatter_(1, label_map.long().cuda(), 1.0) - - # read instance map - if not opt.no_instance: - inst_img = Image.open(inst_path) - inst_img = inst_img.resize((w, h), Image.NEAREST) - self.inst_map = self.toTensor(inst_img).cuda() - self.edge_map = self.get_edges(self.inst_map) - self.net_input = Variable(torch.cat((self.input_label, self.edge_map), dim=1), volatile=True) - else: - self.net_input = Variable(self.input_label, volatile=True) - - self.features_clustered = np.load(feat_path).item() - self.object_map = self.inst_map if opt.instance_feat else self.label_map - - object_np = self.object_map.cpu().numpy().astype(int) - self.feat_map = self.Tensor(1, opt.feat_num, h, w).zero_() - self.cluster_indices = np.zeros(self.opt.label_nc, np.uint8) - for i in np.unique(object_np): - label = i if i < 1000 else i//1000 - if label in self.features_clustered: - feat = self.features_clustered[label] - np.random.seed(i+1) - cluster_idx = np.random.randint(0, feat.shape[0]) - self.cluster_indices[label] = cluster_idx - idx = (self.object_map == i).nonzero() - self.set_features(idx, feat, cluster_idx) - - self.net_input_original = self.net_input.clone() - self.label_map_original = self.label_map.clone() - self.feat_map_original = self.feat_map.clone() - if not opt.no_instance: - self.inst_map_original = self.inst_map.clone() - - def reset(self): - self.net_input = self.net_input_prev = self.net_input_original.clone() - self.label_map = self.label_map_prev = self.label_map_original.clone() - self.feat_map = self.feat_map_prev = self.feat_map_original.clone() - if not self.opt.no_instance: - self.inst_map = self.inst_map_prev = self.inst_map_original.clone() - self.object_map = self.inst_map if self.opt.instance_feat else self.label_map - - def undo(self): - self.net_input = self.net_input_prev - self.label_map = self.label_map_prev - self.feat_map = self.feat_map_prev - if not self.opt.no_instance: - self.inst_map = self.inst_map_prev - self.object_map = self.inst_map if self.opt.instance_feat else self.label_map - - # get boundary map from instance map - def get_edges(self, t): - edge = torch.cuda.ByteTensor(t.size()).zero_() - edge[:,:,:,1:] = edge[:,:,:,1:] | (t[:,:,:,1:] != t[:,:,:,:-1]) - edge[:,:,:,:-1] = edge[:,:,:,:-1] | (t[:,:,:,1:] != t[:,:,:,:-1]) - edge[:,:,1:,:] = edge[:,:,1:,:] | (t[:,:,1:,:] != t[:,:,:-1,:]) - edge[:,:,:-1,:] = edge[:,:,:-1,:] | (t[:,:,1:,:] != t[:,:,:-1,:]) - return edge.float() - - # change the label at the source position to the label at the target position - def change_labels(self, click_src, click_tgt): - y_src, x_src = click_src[0], click_src[1] - y_tgt, x_tgt = click_tgt[0], click_tgt[1] - label_src = int(self.label_map[0, 0, y_src, x_src]) - inst_src = self.inst_map[0, 0, y_src, x_src] - label_tgt = int(self.label_map[0, 0, y_tgt, x_tgt]) - inst_tgt = self.inst_map[0, 0, y_tgt, x_tgt] - - idx_src = (self.inst_map == inst_src).nonzero() - # need to change 3 things: label map, instance map, and feature map - if idx_src.shape: - # backup current maps - self.backup_current_state() - - # change both the label map and the network input - self.label_map[idx_src[:,0], idx_src[:,1], idx_src[:,2], idx_src[:,3]] = label_tgt - self.net_input[idx_src[:,0], idx_src[:,1] + label_src, idx_src[:,2], idx_src[:,3]] = 0 - self.net_input[idx_src[:,0], idx_src[:,1] + label_tgt, idx_src[:,2], idx_src[:,3]] = 1 - - # update the instance map (and the network input) - if inst_tgt > 1000: - # if different instances have different ids, give the new object a new id - tgt_indices = (self.inst_map > label_tgt * 1000) & (self.inst_map < (label_tgt+1) * 1000) - inst_tgt = self.inst_map[tgt_indices].max() + 1 - self.inst_map[idx_src[:,0], idx_src[:,1], idx_src[:,2], idx_src[:,3]] = inst_tgt - self.net_input[:,-1,:,:] = self.get_edges(self.inst_map) - - # also copy the source features to the target position - idx_tgt = (self.inst_map == inst_tgt).nonzero() - if idx_tgt.shape: - self.copy_features(idx_src, idx_tgt[0,:]) - - self.fake_image = util.tensor2im(self.single_forward(self.net_input, self.feat_map)) - - # add strokes of target label in the image - def add_strokes(self, click_src, label_tgt, bw, save): - # get the region of the new strokes (bw is the brush width) - size = self.net_input.size() - h, w = size[2], size[3] - idx_src = torch.LongTensor(bw**2, 4).fill_(0) - for i in range(bw): - idx_src[i*bw:(i+1)*bw, 2] = min(h-1, max(0, click_src[0]-bw//2 + i)) - for j in range(bw): - idx_src[i*bw+j, 3] = min(w-1, max(0, click_src[1]-bw//2 + j)) - idx_src = idx_src.cuda() - - # again, need to update 3 things - if idx_src.shape: - # backup current maps - if save: - self.backup_current_state() - - # update the label map (and the network input) in the stroke region - self.label_map[idx_src[:,0], idx_src[:,1], idx_src[:,2], idx_src[:,3]] = label_tgt - for k in range(self.opt.label_nc): - self.net_input[idx_src[:,0], idx_src[:,1] + k, idx_src[:,2], idx_src[:,3]] = 0 - self.net_input[idx_src[:,0], idx_src[:,1] + label_tgt, idx_src[:,2], idx_src[:,3]] = 1 - - # update the instance map (and the network input) - self.inst_map[idx_src[:,0], idx_src[:,1], idx_src[:,2], idx_src[:,3]] = label_tgt - self.net_input[:,-1,:,:] = self.get_edges(self.inst_map) - - # also update the features if available - if self.opt.instance_feat: - feat = self.features_clustered[label_tgt] - #np.random.seed(label_tgt+1) - #cluster_idx = np.random.randint(0, feat.shape[0]) - cluster_idx = self.cluster_indices[label_tgt] - self.set_features(idx_src, feat, cluster_idx) - - self.fake_image = util.tensor2im(self.single_forward(self.net_input, self.feat_map)) - - # add an object to the clicked position with selected style - def add_objects(self, click_src, label_tgt, mask, style_id=0): - y, x = click_src[0], click_src[1] - mask = np.transpose(mask, (2, 0, 1))[np.newaxis,...] - idx_src = torch.from_numpy(mask).cuda().nonzero() - idx_src[:,2] += y - idx_src[:,3] += x - - # backup current maps - self.backup_current_state() - - # update label map - self.label_map[idx_src[:,0], idx_src[:,1], idx_src[:,2], idx_src[:,3]] = label_tgt - for k in range(self.opt.label_nc): - self.net_input[idx_src[:,0], idx_src[:,1] + k, idx_src[:,2], idx_src[:,3]] = 0 - self.net_input[idx_src[:,0], idx_src[:,1] + label_tgt, idx_src[:,2], idx_src[:,3]] = 1 - - # update instance map - self.inst_map[idx_src[:,0], idx_src[:,1], idx_src[:,2], idx_src[:,3]] = label_tgt - self.net_input[:,-1,:,:] = self.get_edges(self.inst_map) - - # update feature map - self.set_features(idx_src, self.feat, style_id) - - self.fake_image = util.tensor2im(self.single_forward(self.net_input, self.feat_map)) - - def single_forward(self, net_input, feat_map): - net_input = torch.cat((net_input, feat_map), dim=1) - fake_image = self.netG.forward(net_input) - - if fake_image.size()[0] == 1: - return fake_image.data[0] - return fake_image.data - - - # generate all outputs for different styles - def style_forward(self, click_pt, style_id=-1): - if click_pt is None: - self.fake_image = util.tensor2im(self.single_forward(self.net_input, self.feat_map)) - self.crop = None - self.mask = None - else: - instToChange = int(self.object_map[0, 0, click_pt[0], click_pt[1]]) - self.instToChange = instToChange - label = instToChange if instToChange < 1000 else instToChange//1000 - self.feat = self.features_clustered[label] - self.fake_image = [] - self.mask = self.object_map == instToChange - idx = self.mask.nonzero() - self.get_crop_region(idx) - if idx.size(): - if style_id == -1: - (min_y, min_x, max_y, max_x) = self.crop - ### original - for cluster_idx in range(self.opt.multiple_output): - self.set_features(idx, self.feat, cluster_idx) - fake_image = self.single_forward(self.net_input, self.feat_map) - fake_image = util.tensor2im(fake_image[:,min_y:max_y,min_x:max_x]) - self.fake_image.append(fake_image) - """### To speed up previewing different style results, either crop or downsample the label maps - if instToChange > 1000: - (min_y, min_x, max_y, max_x) = self.crop - ### crop - _, _, h, w = self.net_input.size() - offset = 512 - y_start, x_start = max(0, min_y-offset), max(0, min_x-offset) - y_end, x_end = min(h, (max_y + offset)), min(w, (max_x + offset)) - y_region = slice(y_start, y_start+(y_end-y_start)//16*16) - x_region = slice(x_start, x_start+(x_end-x_start)//16*16) - net_input = self.net_input[:,:,y_region,x_region] - for cluster_idx in range(self.opt.multiple_output): - self.set_features(idx, self.feat, cluster_idx) - fake_image = self.single_forward(net_input, self.feat_map[:,:,y_region,x_region]) - fake_image = util.tensor2im(fake_image[:,min_y-y_start:max_y-y_start,min_x-x_start:max_x-x_start]) - self.fake_image.append(fake_image) - else: - ### downsample - (min_y, min_x, max_y, max_x) = [crop//2 for crop in self.crop] - net_input = self.net_input[:,:,::2,::2] - size = net_input.size() - net_input_batch = net_input.expand(self.opt.multiple_output, size[1], size[2], size[3]) - for cluster_idx in range(self.opt.multiple_output): - self.set_features(idx, self.feat, cluster_idx) - feat_map = self.feat_map[:,:,::2,::2] - if cluster_idx == 0: - feat_map_batch = feat_map - else: - feat_map_batch = torch.cat((feat_map_batch, feat_map), dim=0) - fake_image_batch = self.single_forward(net_input_batch, feat_map_batch) - for i in range(self.opt.multiple_output): - self.fake_image.append(util.tensor2im(fake_image_batch[i,:,min_y:max_y,min_x:max_x]))""" - - else: - self.set_features(idx, self.feat, style_id) - self.cluster_indices[label] = style_id - self.fake_image = util.tensor2im(self.single_forward(self.net_input, self.feat_map)) - - def backup_current_state(self): - self.net_input_prev = self.net_input.clone() - self.label_map_prev = self.label_map.clone() - self.inst_map_prev = self.inst_map.clone() - self.feat_map_prev = self.feat_map.clone() - - # crop the ROI and get the mask of the object - def get_crop_region(self, idx): - size = self.net_input.size() - h, w = size[2], size[3] - min_y, min_x = idx[:,2].min(), idx[:,3].min() - max_y, max_x = idx[:,2].max(), idx[:,3].max() - crop_min = 128 - if max_y - min_y < crop_min: - min_y = max(0, (max_y + min_y) // 2 - crop_min // 2) - max_y = min(h-1, min_y + crop_min) - if max_x - min_x < crop_min: - min_x = max(0, (max_x + min_x) // 2 - crop_min // 2) - max_x = min(w-1, min_x + crop_min) - self.crop = (min_y, min_x, max_y, max_x) - self.mask = self.mask[:,:, min_y:max_y, min_x:max_x] - - # update the feature map once a new object is added or the label is changed - def update_features(self, cluster_idx, mask=None, click_pt=None): - self.feat_map_prev = self.feat_map.clone() - # adding a new object - if mask is not None: - y, x = click_pt[0], click_pt[1] - mask = np.transpose(mask, (2,0,1))[np.newaxis,...] - idx = torch.from_numpy(mask).cuda().nonzero() - idx[:,2] += y - idx[:,3] += x - # changing the label of an existing object - else: - idx = (self.object_map == self.instToChange).nonzero() - - # update feature map - self.set_features(idx, self.feat, cluster_idx) - - # set the class features to the target feature - def set_features(self, idx, feat, cluster_idx): - for k in range(self.opt.feat_num): - self.feat_map[idx[:,0], idx[:,1] + k, idx[:,2], idx[:,3]] = feat[cluster_idx, k] - - # copy the features at the target position to the source position - def copy_features(self, idx_src, idx_tgt): - for k in range(self.opt.feat_num): - val = self.feat_map[idx_tgt[0], idx_tgt[1] + k, idx_tgt[2], idx_tgt[3]] - self.feat_map[idx_src[:,0], idx_src[:,1] + k, idx_src[:,2], idx_src[:,3]] = val - - def get_current_visuals(self, getLabel=False): - mask = self.mask - if self.mask is not None: - mask = np.transpose(self.mask[0].cpu().float().numpy(), (1,2,0)).astype(np.uint8) - - dict_list = [('fake_image', self.fake_image), ('mask', mask)] - - if getLabel: # only output label map if needed to save bandwidth - label = util.tensor2label(self.net_input.data[0], self.opt.label_nc) - dict_list += [('label', label)] - - return OrderedDict(dict_list) \ No newline at end of file diff --git a/pix2pixHD_attack/options/__init__.py b/pix2pixHD_attack/options/__init__.py deleted file mode 100755 index e69de29..0000000 diff --git a/pix2pixHD_attack/options/base_options.py b/pix2pixHD_attack/options/base_options.py deleted file mode 100755 index 0d5e769..0000000 --- a/pix2pixHD_attack/options/base_options.py +++ /dev/null @@ -1,99 +0,0 @@ -import argparse -import os -from util import util -import torch - -class BaseOptions(): - def __init__(self): - self.parser = argparse.ArgumentParser() - self.initialized = False - - def initialize(self): - # experiment specifics - self.parser.add_argument('--name', type=str, default='label2city', help='name of the experiment. It decides where to store samples and models') - self.parser.add_argument('--gpu_ids', type=str, default='0', help='gpu ids: e.g. 0 0,1,2, 0,2. use -1 for CPU') - self.parser.add_argument('--checkpoints_dir', type=str, default='./checkpoints', help='models are saved here') - self.parser.add_argument('--model', type=str, default='pix2pixHD', help='which model to use') - self.parser.add_argument('--norm', type=str, default='instance', help='instance normalization or batch normalization') - self.parser.add_argument('--use_dropout', action='store_true', help='use dropout for the generator') - self.parser.add_argument('--data_type', default=32, type=int, choices=[8, 16, 32], help="Supported data type i.e. 8, 16, 32 bit") - self.parser.add_argument('--verbose', action='store_true', default=False, help='toggles verbose') - self.parser.add_argument('--fp16', action='store_true', default=False, help='train with AMP') - self.parser.add_argument('--local_rank', type=int, default=0, help='local rank for distributed training') - - # input/output sizes - self.parser.add_argument('--batchSize', type=int, default=1, help='input batch size') - self.parser.add_argument('--loadSize', type=int, default=1024, help='scale images to this size') - self.parser.add_argument('--fineSize', type=int, default=512, help='then crop to this size') - self.parser.add_argument('--label_nc', type=int, default=35, help='# of input label channels') - self.parser.add_argument('--input_nc', type=int, default=3, help='# of input image channels') - self.parser.add_argument('--output_nc', type=int, default=3, help='# of output image channels') - - # for setting inputs - self.parser.add_argument('--dataroot', type=str, default='./datasets/cityscapes/') - self.parser.add_argument('--resize_or_crop', type=str, default='scale_width', help='scaling and cropping of images at load time [resize_and_crop|crop|scale_width|scale_width_and_crop]') - self.parser.add_argument('--serial_batches', action='store_true', help='if true, takes images in order to make batches, otherwise takes them randomly') - self.parser.add_argument('--no_flip', action='store_true', help='if specified, do not flip the images for data argumentation') - self.parser.add_argument('--nThreads', default=2, type=int, help='# threads for loading data') - self.parser.add_argument('--max_dataset_size', type=int, default=float("inf"), help='Maximum number of samples allowed per dataset. If the dataset directory contains more than max_dataset_size, only a subset is loaded.') - - # for displays - self.parser.add_argument('--display_winsize', type=int, default=512, help='display window size') - self.parser.add_argument('--tf_log', action='store_true', help='if specified, use tensorboard logging. Requires tensorflow installed') - - # for generator - self.parser.add_argument('--netG', type=str, default='global', help='selects model to use for netG') - self.parser.add_argument('--ngf', type=int, default=64, help='# of gen filters in first conv layer') - self.parser.add_argument('--n_downsample_global', type=int, default=4, help='number of downsampling layers in netG') - self.parser.add_argument('--n_blocks_global', type=int, default=9, help='number of residual blocks in the global generator network') - self.parser.add_argument('--n_blocks_local', type=int, default=3, help='number of residual blocks in the local enhancer network') - self.parser.add_argument('--n_local_enhancers', type=int, default=1, help='number of local enhancers to use') - self.parser.add_argument('--niter_fix_global', type=int, default=0, help='number of epochs that we only train the outmost local enhancer') - - # for instance-wise features - self.parser.add_argument('--no_instance', action='store_true', help='if specified, do *not* add instance map as input') - self.parser.add_argument('--instance_feat', action='store_true', help='if specified, add encoded instance features as input') - self.parser.add_argument('--label_feat', action='store_true', help='if specified, add encoded label features as input') - self.parser.add_argument('--feat_num', type=int, default=3, help='vector length for encoded features') - self.parser.add_argument('--load_features', action='store_true', help='if specified, load precomputed feature maps') - self.parser.add_argument('--n_downsample_E', type=int, default=4, help='# of downsampling layers in encoder') - self.parser.add_argument('--nef', type=int, default=16, help='# of encoder filters in the first conv layer') - self.parser.add_argument('--n_clusters', type=int, default=10, help='number of clusters for features') - - self.initialized = True - - def parse(self, save=True): - if not self.initialized: - self.initialize() - self.opt = self.parser.parse_args() - self.opt.isTrain = self.isTrain # train or test - - str_ids = self.opt.gpu_ids.split(',') - self.opt.gpu_ids = [] - for str_id in str_ids: - id = int(str_id) - if id >= 0: - self.opt.gpu_ids.append(id) - - # set gpu ids - if len(self.opt.gpu_ids) > 0: - torch.cuda.set_device(self.opt.gpu_ids[0]) - - args = vars(self.opt) - - print('------------ Options -------------') - for k, v in sorted(args.items()): - print('%s: %s' % (str(k), str(v))) - print('-------------- End ----------------') - - # save to the disk - expr_dir = os.path.join(self.opt.checkpoints_dir, self.opt.name) - util.mkdirs(expr_dir) - if save and not self.opt.continue_train: - file_name = os.path.join(expr_dir, 'opt.txt') - with open(file_name, 'wt') as opt_file: - opt_file.write('------------ Options -------------\n') - for k, v in sorted(args.items()): - opt_file.write('%s: %s\n' % (str(k), str(v))) - opt_file.write('-------------- End ----------------\n') - return self.opt diff --git a/pix2pixHD_attack/options/test_options.py b/pix2pixHD_attack/options/test_options.py deleted file mode 100755 index 5604347..0000000 --- a/pix2pixHD_attack/options/test_options.py +++ /dev/null @@ -1,17 +0,0 @@ -from .base_options import BaseOptions - -class TestOptions(BaseOptions): - def initialize(self): - BaseOptions.initialize(self) - self.parser.add_argument('--ntest', type=int, default=float("inf"), help='# of test examples.') - self.parser.add_argument('--results_dir', type=str, default='./results/', help='saves results here.') - self.parser.add_argument('--aspect_ratio', type=float, default=1.0, help='aspect ratio of result images') - self.parser.add_argument('--phase', type=str, default='test', help='train, val, test, etc') - self.parser.add_argument('--which_epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') - self.parser.add_argument('--how_many', type=int, default=100, help='how many test images to run') - self.parser.add_argument('--cluster_path', type=str, default='features_clustered_010.npy', help='the path for clustered results of encoded features') - self.parser.add_argument('--use_encoded_image', action='store_true', help='if specified, encode the real image to get the feature map') - self.parser.add_argument("--export_onnx", type=str, help="export ONNX model to a given file") - self.parser.add_argument("--engine", type=str, help="run serialized TRT engine") - self.parser.add_argument("--onnx", type=str, help="run ONNX model via TRT") - self.isTrain = False diff --git a/pix2pixHD_attack/options/train_options.py b/pix2pixHD_attack/options/train_options.py deleted file mode 100755 index cacb8e7..0000000 --- a/pix2pixHD_attack/options/train_options.py +++ /dev/null @@ -1,34 +0,0 @@ -from .base_options import BaseOptions - -class TrainOptions(BaseOptions): - def initialize(self): - BaseOptions.initialize(self) - # for displays - self.parser.add_argument('--display_freq', type=int, default=100, help='frequency of showing training results on screen') - self.parser.add_argument('--print_freq', type=int, default=100, help='frequency of showing training results on console') - self.parser.add_argument('--save_latest_freq', type=int, default=1000, help='frequency of saving the latest results') - self.parser.add_argument('--save_epoch_freq', type=int, default=10, help='frequency of saving checkpoints at the end of epochs') - self.parser.add_argument('--no_html', action='store_true', help='do not save intermediate training results to [opt.checkpoints_dir]/[opt.name]/web/') - self.parser.add_argument('--debug', action='store_true', help='only do one epoch and displays at each iteration') - - # for training - self.parser.add_argument('--continue_train', action='store_true', help='continue training: load the latest model') - self.parser.add_argument('--load_pretrain', type=str, default='', help='load the pretrained model from the specified location') - self.parser.add_argument('--which_epoch', type=str, default='latest', help='which epoch to load? set to latest to use latest cached model') - self.parser.add_argument('--phase', type=str, default='train', help='train, val, test, etc') - self.parser.add_argument('--niter', type=int, default=100, help='# of iter at starting learning rate') - self.parser.add_argument('--niter_decay', type=int, default=100, help='# of iter to linearly decay learning rate to zero') - self.parser.add_argument('--beta1', type=float, default=0.5, help='momentum term of adam') - self.parser.add_argument('--lr', type=float, default=0.0002, help='initial learning rate for adam') - - # for discriminators - self.parser.add_argument('--num_D', type=int, default=2, help='number of discriminators to use') - self.parser.add_argument('--n_layers_D', type=int, default=3, help='only used if which_model_netD==n_layers') - self.parser.add_argument('--ndf', type=int, default=64, help='# of discrim filters in first conv layer') - self.parser.add_argument('--lambda_feat', type=float, default=10.0, help='weight for feature matching loss') - self.parser.add_argument('--no_ganFeat_loss', action='store_true', help='if specified, do *not* use discriminator feature matching loss') - self.parser.add_argument('--no_vgg_loss', action='store_true', help='if specified, do *not* use VGG feature matching loss') - self.parser.add_argument('--no_lsgan', action='store_true', help='do *not* use least square GAN, if false, use vanilla GAN') - self.parser.add_argument('--pool_size', type=int, default=0, help='the size of image buffer that stores previously generated images') - - self.isTrain = True diff --git a/pix2pixHD_attack/precompute_feature_maps.py b/pix2pixHD_attack/precompute_feature_maps.py deleted file mode 100755 index 8836ea2..0000000 --- a/pix2pixHD_attack/precompute_feature_maps.py +++ /dev/null @@ -1,33 +0,0 @@ -from options.train_options import TrainOptions -from data.data_loader import CreateDataLoader -from models.models import create_model -import os -import util.util as util -from torch.autograd import Variable -import torch.nn as nn - -opt = TrainOptions().parse() -opt.nThreads = 1 -opt.batchSize = 1 -opt.serial_batches = True -opt.no_flip = True -opt.instance_feat = True - -name = 'features' -save_path = os.path.join(opt.checkpoints_dir, opt.name) - -############ Initialize ######### -data_loader = CreateDataLoader(opt) -dataset = data_loader.load_data() -dataset_size = len(data_loader) -model = create_model(opt) -util.mkdirs(os.path.join(opt.dataroot, opt.phase + '_feat')) - -######## Save precomputed feature maps for 1024p training ####### -for i, data in enumerate(dataset): - print('%d / %d images' % (i+1, dataset_size)) - feat_map = model.module.netE.forward(Variable(data['image'].cuda(), volatile=True), data['inst'].cuda()) - feat_map = nn.Upsample(scale_factor=2, mode='nearest')(feat_map) - image_numpy = util.tensor2im(feat_map.data[0]) - save_path = data['path'][0].replace('/train_label/', '/train_feat/') - util.save_image(image_numpy, save_path) \ No newline at end of file diff --git a/pix2pixHD_attack/run_engine.py b/pix2pixHD_attack/run_engine.py deleted file mode 100644 index 700494d..0000000 --- a/pix2pixHD_attack/run_engine.py +++ /dev/null @@ -1,173 +0,0 @@ -import os -import sys -from random import randint -import numpy as np -import tensorrt - -try: - from PIL import Image - import pycuda.driver as cuda - import pycuda.gpuarray as gpuarray - import pycuda.autoinit - import argparse -except ImportError as err: - sys.stderr.write("""ERROR: failed to import module ({}) -Please make sure you have pycuda and the example dependencies installed. -https://wiki.tiker.net/PyCuda/Installation/Linux -pip(3) install tensorrt[examples] -""".format(err)) - exit(1) - -try: - import tensorrt as trt - from tensorrt.parsers import caffeparser - from tensorrt.parsers import onnxparser -except ImportError as err: - sys.stderr.write("""ERROR: failed to import module ({}) -Please make sure you have the TensorRT Library installed -and accessible in your LD_LIBRARY_PATH -""".format(err)) - exit(1) - - -G_LOGGER = trt.infer.ConsoleLogger(trt.infer.LogSeverity.INFO) - -class Profiler(trt.infer.Profiler): - """ - Example Implimentation of a Profiler - Is identical to the Profiler class in trt.infer so it is possible - to just use that instead of implementing this if further - functionality is not needed - """ - def __init__(self, timing_iter): - trt.infer.Profiler.__init__(self) - self.timing_iterations = timing_iter - self.profile = [] - - def report_layer_time(self, layerName, ms): - record = next((r for r in self.profile if r[0] == layerName), (None, None)) - if record == (None, None): - self.profile.append((layerName, ms)) - else: - self.profile[self.profile.index(record)] = (record[0], record[1] + ms) - - def print_layer_times(self): - totalTime = 0 - for i in range(len(self.profile)): - print("{:40.40} {:4.3f}ms".format(self.profile[i][0], self.profile[i][1] / self.timing_iterations)) - totalTime += self.profile[i][1] - print("Time over all layers: {:4.2f} ms per iteration".format(totalTime / self.timing_iterations)) - - -def get_input_output_names(trt_engine): - nbindings = trt_engine.get_nb_bindings(); - maps = [] - - for b in range(0, nbindings): - dims = trt_engine.get_binding_dimensions(b).to_DimsCHW() - name = trt_engine.get_binding_name(b) - type = trt_engine.get_binding_data_type(b) - - if (trt_engine.binding_is_input(b)): - maps.append(name) - print("Found input: ", name) - else: - maps.append(name) - print("Found output: ", name) - - print("shape=" + str(dims.C()) + " , " + str(dims.H()) + " , " + str(dims.W())) - print("dtype=" + str(type)) - return maps - -def create_memory(engine, name, buf, mem, batchsize, inp, inp_idx): - binding_idx = engine.get_binding_index(name) - if binding_idx == -1: - raise AttributeError("Not a valid binding") - print("Binding: name={}, bindingIndex={}".format(name, str(binding_idx))) - dims = engine.get_binding_dimensions(binding_idx).to_DimsCHW() - eltCount = dims.C() * dims.H() * dims.W() * batchsize - - if engine.binding_is_input(binding_idx): - h_mem = inp[inp_idx] - inp_idx = inp_idx + 1 - else: - h_mem = np.random.uniform(0.0, 255.0, eltCount).astype(np.dtype('f4')) - - d_mem = cuda.mem_alloc(eltCount * 4) - cuda.memcpy_htod(d_mem, h_mem) - buf.insert(binding_idx, int(d_mem)) - mem.append(d_mem) - return inp_idx - - -#Run inference on device -def time_inference(engine, batch_size, inp): - bindings = [] - mem = [] - inp_idx = 0 - for io in get_input_output_names(engine): - inp_idx = create_memory(engine, io, bindings, mem, - batch_size, inp, inp_idx) - - context = engine.create_execution_context() - g_prof = Profiler(500) - context.set_profiler(g_prof) - for i in range(iter): - context.execute(batch_size, bindings) - g_prof.print_layer_times() - - context.destroy() - return - - -def convert_to_datatype(v): - if v==8: - return trt.infer.DataType.INT8 - elif v==16: - return trt.infer.DataType.HALF - elif v==32: - return trt.infer.DataType.FLOAT - else: - print("ERROR: Invalid model data type bit depth: " + str(v)) - return trt.infer.DataType.INT8 - -def run_trt_engine(engine_file, bs, it): - engine = trt.utils.load_engine(G_LOGGER, engine_file) - time_inference(engine, bs, it) - -def run_onnx(onnx_file, data_type, bs, inp): - # Create onnx_config - apex = onnxparser.create_onnxconfig() - apex.set_model_file_name(onnx_file) - apex.set_model_dtype(convert_to_datatype(data_type)) - - # create parser - trt_parser = onnxparser.create_onnxparser(apex) - assert(trt_parser) - data_type = apex.get_model_dtype() - onnx_filename = apex.get_model_file_name() - trt_parser.parse(onnx_filename, data_type) - trt_parser.report_parsing_info() - trt_parser.convert_to_trtnetwork() - trt_network = trt_parser.get_trtnetwork() - assert(trt_network) - - # create infer builder - trt_builder = trt.infer.create_infer_builder(G_LOGGER) - trt_builder.set_max_batch_size(max_batch_size) - trt_builder.set_max_workspace_size(max_workspace_size) - - if (apex.get_model_dtype() == trt.infer.DataType_kHALF): - print("------------------- Running FP16 -----------------------------") - trt_builder.set_half2_mode(True) - elif (apex.get_model_dtype() == trt.infer.DataType_kINT8): - print("------------------- Running INT8 -----------------------------") - trt_builder.set_int8_mode(True) - else: - print("------------------- Running FP32 -----------------------------") - - print("----- Builder is Done -----") - print("----- Creating Engine -----") - trt_engine = trt_builder.build_cuda_engine(trt_network) - print("----- Engine is built -----") - time_inference(engine, bs, inp) diff --git a/pix2pixHD_attack/scripts/test_1024p.sh b/pix2pixHD_attack/scripts/test_1024p.sh deleted file mode 100755 index 319803c..0000000 --- a/pix2pixHD_attack/scripts/test_1024p.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -################################ Testing ################################ -# labels only -python test.py --name label2city_1024p --netG local --ngf 32 --resize_or_crop none $@ diff --git a/pix2pixHD_attack/scripts/test_1024p_feat.sh b/pix2pixHD_attack/scripts/test_1024p_feat.sh deleted file mode 100755 index 2f4ba17..0000000 --- a/pix2pixHD_attack/scripts/test_1024p_feat.sh +++ /dev/null @@ -1,5 +0,0 @@ -################################ Testing ################################ -# first precompute and cluster all features -python encode_features.py --name label2city_1024p_feat --netG local --ngf 32 --resize_or_crop none; -# use instance-wise features -python test.py --name label2city_1024p_feat ---netG local --ngf 32 --resize_or_crop none --instance_feat \ No newline at end of file diff --git a/pix2pixHD_attack/scripts/test_512p.sh b/pix2pixHD_attack/scripts/test_512p.sh deleted file mode 100755 index 3131043..0000000 --- a/pix2pixHD_attack/scripts/test_512p.sh +++ /dev/null @@ -1,3 +0,0 @@ -################################ Testing ################################ -# labels only -python test.py --name label2city_512p \ No newline at end of file diff --git a/pix2pixHD_attack/scripts/test_512p_feat.sh b/pix2pixHD_attack/scripts/test_512p_feat.sh deleted file mode 100755 index 8f25e9c..0000000 --- a/pix2pixHD_attack/scripts/test_512p_feat.sh +++ /dev/null @@ -1,5 +0,0 @@ -################################ Testing ################################ -# first precompute and cluster all features -python encode_features.py --name label2city_512p_feat; -# use instance-wise features -python test.py --name label2city_512p_feat --instance_feat \ No newline at end of file diff --git a/pix2pixHD_attack/scripts/train_1024p_12G.sh b/pix2pixHD_attack/scripts/train_1024p_12G.sh deleted file mode 100755 index d5ea7d7..0000000 --- a/pix2pixHD_attack/scripts/train_1024p_12G.sh +++ /dev/null @@ -1,4 +0,0 @@ -############## To train images at 2048 x 1024 resolution after training 1024 x 512 resolution models ############# -##### Using GPUs with 12G memory (not tested) -# Using labels only -python train.py --name label2city_1024p --netG local --ngf 32 --num_D 3 --load_pretrain checkpoints/label2city_512p/ --niter_fix_global 20 --resize_or_crop crop --fineSize 1024 \ No newline at end of file diff --git a/pix2pixHD_attack/scripts/train_1024p_24G.sh b/pix2pixHD_attack/scripts/train_1024p_24G.sh deleted file mode 100755 index 88e58f7..0000000 --- a/pix2pixHD_attack/scripts/train_1024p_24G.sh +++ /dev/null @@ -1,4 +0,0 @@ -############## To train images at 2048 x 1024 resolution after training 1024 x 512 resolution models ############# -######## Using GPUs with 24G memory -# Using labels only -python train.py --name label2city_1024p --netG local --ngf 32 --num_D 3 --load_pretrain checkpoints/label2city_512p/ --niter 50 --niter_decay 50 --niter_fix_global 10 --resize_or_crop none \ No newline at end of file diff --git a/pix2pixHD_attack/scripts/train_1024p_feat_12G.sh b/pix2pixHD_attack/scripts/train_1024p_feat_12G.sh deleted file mode 100755 index f8e3d61..0000000 --- a/pix2pixHD_attack/scripts/train_1024p_feat_12G.sh +++ /dev/null @@ -1,6 +0,0 @@ -############## To train images at 2048 x 1024 resolution after training 1024 x 512 resolution models ############# -##### Using GPUs with 12G memory (not tested) -# First precompute feature maps and save them -python precompute_feature_maps.py --name label2city_512p_feat; -# Adding instances and encoded features -python train.py --name label2city_1024p_feat --netG local --ngf 32 --num_D 3 --load_pretrain checkpoints/label2city_512p_feat/ --niter_fix_global 20 --resize_or_crop crop --fineSize 896 --instance_feat --load_features \ No newline at end of file diff --git a/pix2pixHD_attack/scripts/train_1024p_feat_24G.sh b/pix2pixHD_attack/scripts/train_1024p_feat_24G.sh deleted file mode 100755 index 399d720..0000000 --- a/pix2pixHD_attack/scripts/train_1024p_feat_24G.sh +++ /dev/null @@ -1,6 +0,0 @@ -############## To train images at 2048 x 1024 resolution after training 1024 x 512 resolution models ############# -######## Using GPUs with 24G memory -# First precompute feature maps and save them -python precompute_feature_maps.py --name label2city_512p_feat; -# Adding instances and encoded features -python train.py --name label2city_1024p_feat --netG local --ngf 32 --num_D 3 --load_pretrain checkpoints/label2city_512p_feat/ --niter 50 --niter_decay 50 --niter_fix_global 10 --resize_or_crop none --instance_feat --load_features \ No newline at end of file diff --git a/pix2pixHD_attack/scripts/train_512p.sh b/pix2pixHD_attack/scripts/train_512p.sh deleted file mode 100755 index 222c348..0000000 --- a/pix2pixHD_attack/scripts/train_512p.sh +++ /dev/null @@ -1,2 +0,0 @@ -### Using labels only -python train.py --name label2city_512p \ No newline at end of file diff --git a/pix2pixHD_attack/scripts/train_512p_feat.sh b/pix2pixHD_attack/scripts/train_512p_feat.sh deleted file mode 100755 index 9d4859c..0000000 --- a/pix2pixHD_attack/scripts/train_512p_feat.sh +++ /dev/null @@ -1,2 +0,0 @@ -### Adding instances and encoded features -python train.py --name label2city_512p_feat --instance_feat \ No newline at end of file diff --git a/pix2pixHD_attack/scripts/train_512p_fp16.sh b/pix2pixHD_attack/scripts/train_512p_fp16.sh deleted file mode 100755 index 2bd5e07..0000000 --- a/pix2pixHD_attack/scripts/train_512p_fp16.sh +++ /dev/null @@ -1,2 +0,0 @@ -### Using labels only - python -m torch.distributed.launch train.py --name label2city_512p --fp16 \ No newline at end of file diff --git a/pix2pixHD_attack/scripts/train_512p_fp16_multigpu.sh b/pix2pixHD_attack/scripts/train_512p_fp16_multigpu.sh deleted file mode 100755 index 0d9686c..0000000 --- a/pix2pixHD_attack/scripts/train_512p_fp16_multigpu.sh +++ /dev/null @@ -1,2 +0,0 @@ -######## Multi-GPU training example ####### -python -m torch.distributed.launch train.py --name label2city_512p --batchSize 8 --gpu_ids 0,1,2,3,4,5,6,7 --fp16 \ No newline at end of file diff --git a/pix2pixHD_attack/scripts/train_512p_multigpu.sh b/pix2pixHD_attack/scripts/train_512p_multigpu.sh deleted file mode 100755 index 16f0a1a..0000000 --- a/pix2pixHD_attack/scripts/train_512p_multigpu.sh +++ /dev/null @@ -1,2 +0,0 @@ -######## Multi-GPU training example ####### -python train.py --name label2city_512p --batchSize 8 --gpu_ids 0,1,2,3,4,5,6,7 \ No newline at end of file diff --git a/pix2pixHD_attack/test.py b/pix2pixHD_attack/test.py deleted file mode 100755 index a1d890a..0000000 --- a/pix2pixHD_attack/test.py +++ /dev/null @@ -1,94 +0,0 @@ -import os -from collections import OrderedDict -from torch.autograd import Variable -from options.test_options import TestOptions -from data.data_loader import CreateDataLoader -from models.models import create_model -import util.util as util -from util.visualizer import Visualizer -from util import html -import torch -import torch.nn.functional as F - -opt = TestOptions().parse(save=False) -opt.nThreads = 1 # test code only supports nThreads = 1 -opt.batchSize = 1 # test code only supports batchSize = 1 -opt.serial_batches = True # no shuffle -opt.no_flip = True # no flip - -data_loader = CreateDataLoader(opt) -dataset = data_loader.load_data() -visualizer = Visualizer(opt) -# create website -web_dir = os.path.join(opt.results_dir, opt.name, '%s_%s' % (opt.phase, opt.which_epoch)) -webpage = html.HTML(web_dir, 'Experiment = %s, Phase = %s, Epoch = %s' % (opt.name, opt.phase, opt.which_epoch)) - -# test -if not opt.engine and not opt.onnx: - model = create_model(opt) - if opt.data_type == 16: - model.half() - elif opt.data_type == 8: - model.type(torch.uint8) - - if opt.verbose: - print(model) -else: - from run_engine import run_trt_engine, run_onnx - -# Initialize Metrics -l1_error, l2_error, min_dist, l0_error, perceptual_error = 0.0, 0.0, 0.0, 0.0, 0.0 -n_samples = 0 - -for i, data in enumerate(dataset): - if i >= opt.how_many: - break - if opt.data_type == 16: - data['label'] = data['label'].half() - data['inst'] = data['inst'].half() - elif opt.data_type == 8: - data['label'] = data['label'].uint8() - data['inst'] = data['inst'].uint8() - if opt.export_onnx: - print ("Exporting to ONNX: ", opt.export_onnx) - assert opt.export_onnx.endswith("onnx"), "Export model file should end with .onnx" - torch.onnx.export(model, [data['label'], data['inst']], - opt.export_onnx, verbose=True) - exit(0) - minibatch = 1 - - # Transfer - # if i == 0: - # adv_image, perturb = model.attack(data['label'], data['inst'], data['image']) - if opt.engine: - generated = run_trt_engine(opt.engine, minibatch, [data['label'], data['inst']]) - elif opt.onnx: - generated = run_onnx(opt.onnx, opt.data_type, minibatch, [data['label'], data['inst']]) - else: - generated_noattack = model.inference(data['label'], data['inst'], data['image']) - adv_image, perturb = model.attack(data['label'], data['inst'], data['image']) - generated, adv_img = model.inference_attack(data['label'], data['inst'], data['image'], perturb) - - visuals = OrderedDict([('original_label', util.tensor2label(data['label'][0], opt.label_nc)), - ('input_label', util.tensor2label(adv_img.data[0], opt.label_nc)), - ('attacked_image', util.tensor2im(generated.data[0])), - ('noattack', util.tensor2im(generated_noattack.data[0]))]) - img_path = data['path'] - print('process image... %s' % img_path) - visualizer.save_images(webpage, visuals, img_path) - - # Compute metrics - l1_error += F.l1_loss(generated, generated_noattack) - l2_error += F.mse_loss(generated, generated_noattack) - l0_error += (generated - generated_noattack).norm(0) - min_dist += (generated - generated_noattack).norm(float('-inf')) - n_samples += 1 - - # generated, genereated_noattack, adv_image = None, None, None - generated, genereated_noattack, adv_image, perturb = None, None, None, None - -# Print metrics -print('{} images. L1 error: {}. L2 error: {}. L0 error: {}. L_-inf error: {}. Perceptual error: {}.'.format(n_samples, -l1_error / n_samples, l2_error / n_samples, l0_error / n_samples, min_dist / n_samples, perceptual_error / n_samples)) - -webpage.save() diff --git a/pix2pixHD_attack/train.py b/pix2pixHD_attack/train.py deleted file mode 100755 index acedac2..0000000 --- a/pix2pixHD_attack/train.py +++ /dev/null @@ -1,141 +0,0 @@ -import time -import os -import numpy as np -import torch -from torch.autograd import Variable -from collections import OrderedDict -from subprocess import call -import fractions -def lcm(a,b): return abs(a * b)/fractions.gcd(a,b) if a and b else 0 - -from options.train_options import TrainOptions -from data.data_loader import CreateDataLoader -from models.models import create_model -import util.util as util -from util.visualizer import Visualizer - -opt = TrainOptions().parse() -iter_path = os.path.join(opt.checkpoints_dir, opt.name, 'iter.txt') -if opt.continue_train: - try: - start_epoch, epoch_iter = np.loadtxt(iter_path , delimiter=',', dtype=int) - except: - start_epoch, epoch_iter = 1, 0 - print('Resuming from epoch %d at iteration %d' % (start_epoch, epoch_iter)) -else: - start_epoch, epoch_iter = 1, 0 - -opt.print_freq = lcm(opt.print_freq, opt.batchSize) -if opt.debug: - opt.display_freq = 1 - opt.print_freq = 1 - opt.niter = 1 - opt.niter_decay = 0 - opt.max_dataset_size = 10 - -data_loader = CreateDataLoader(opt) -dataset = data_loader.load_data() -dataset_size = len(data_loader) -print('#training images = %d' % dataset_size) - -model = create_model(opt) -visualizer = Visualizer(opt) -if opt.fp16: - from apex import amp - model, [optimizer_G, optimizer_D] = amp.initialize(model, [model.optimizer_G, model.optimizer_D], opt_level='O1') - model = torch.nn.DataParallel(model, device_ids=opt.gpu_ids) -else: - optimizer_G, optimizer_D = model.module.optimizer_G, model.module.optimizer_D - -total_steps = (start_epoch-1) * dataset_size + epoch_iter - -display_delta = total_steps % opt.display_freq -print_delta = total_steps % opt.print_freq -save_delta = total_steps % opt.save_latest_freq - -for epoch in range(start_epoch, opt.niter + opt.niter_decay + 1): - epoch_start_time = time.time() - if epoch != start_epoch: - epoch_iter = epoch_iter % dataset_size - for i, data in enumerate(dataset, start=epoch_iter): - if total_steps % opt.print_freq == print_delta: - iter_start_time = time.time() - total_steps += opt.batchSize - epoch_iter += opt.batchSize - - # whether to collect output images - save_fake = total_steps % opt.display_freq == display_delta - - ############## Forward Pass ###################### - losses, generated = model(Variable(data['label']), Variable(data['inst']), - Variable(data['image']), Variable(data['feat']), infer=save_fake) - - # sum per device losses - losses = [ torch.mean(x) if not isinstance(x, int) else x for x in losses ] - loss_dict = dict(zip(model.module.loss_names, losses)) - - # calculate final loss scalar - loss_D = (loss_dict['D_fake'] + loss_dict['D_real']) * 0.5 - loss_G = loss_dict['G_GAN'] + loss_dict.get('G_GAN_Feat',0) + loss_dict.get('G_VGG',0) - - ############### Backward Pass #################### - # update generator weights - optimizer_G.zero_grad() - if opt.fp16: - with amp.scale_loss(loss_G, optimizer_G) as scaled_loss: scaled_loss.backward() - else: - loss_G.backward() - optimizer_G.step() - - # update discriminator weights - optimizer_D.zero_grad() - if opt.fp16: - with amp.scale_loss(loss_D, optimizer_D) as scaled_loss: scaled_loss.backward() - else: - loss_D.backward() - optimizer_D.step() - - ############## Display results and errors ########## - ### print out errors - if total_steps % opt.print_freq == print_delta: - errors = {k: v.data.item() if not isinstance(v, int) else v for k, v in loss_dict.items()} - t = (time.time() - iter_start_time) / opt.print_freq - visualizer.print_current_errors(epoch, epoch_iter, errors, t) - visualizer.plot_current_errors(errors, total_steps) - #call(["nvidia-smi", "--format=csv", "--query-gpu=memory.used,memory.free"]) - - ### display output images - if save_fake: - visuals = OrderedDict([('input_label', util.tensor2label(data['label'][0], opt.label_nc)), - ('synthesized_image', util.tensor2im(generated.data[0])), - ('real_image', util.tensor2im(data['image'][0]))]) - visualizer.display_current_results(visuals, epoch, total_steps) - - ### save latest model - if total_steps % opt.save_latest_freq == save_delta: - print('saving the latest model (epoch %d, total_steps %d)' % (epoch, total_steps)) - model.module.save('latest') - np.savetxt(iter_path, (epoch, epoch_iter), delimiter=',', fmt='%d') - - if epoch_iter >= dataset_size: - break - - # end of epoch - iter_end_time = time.time() - print('End of epoch %d / %d \t Time Taken: %d sec' % - (epoch, opt.niter + opt.niter_decay, time.time() - epoch_start_time)) - - ### save model for this epoch - if epoch % opt.save_epoch_freq == 0: - print('saving the model at the end of epoch %d, iters %d' % (epoch, total_steps)) - model.module.save('latest') - model.module.save(epoch) - np.savetxt(iter_path, (epoch+1, 0), delimiter=',', fmt='%d') - - ### instead of only training the local enhancer, train the entire network after certain iterations - if (opt.niter_fix_global != 0) and (epoch == opt.niter_fix_global): - model.module.update_fixed_params() - - ### linearly decay learning rate after certain iterations - if epoch > opt.niter: - model.module.update_learning_rate() diff --git a/pix2pixHD_attack/util/__init__.py b/pix2pixHD_attack/util/__init__.py deleted file mode 100755 index e69de29..0000000 diff --git a/pix2pixHD_attack/util/html.py b/pix2pixHD_attack/util/html.py deleted file mode 100755 index 71c48ad..0000000 --- a/pix2pixHD_attack/util/html.py +++ /dev/null @@ -1,63 +0,0 @@ -import dominate -from dominate.tags import * -import os - - -class HTML: - def __init__(self, web_dir, title, refresh=0): - self.title = title - self.web_dir = web_dir - self.img_dir = os.path.join(self.web_dir, 'images') - if not os.path.exists(self.web_dir): - os.makedirs(self.web_dir) - if not os.path.exists(self.img_dir): - os.makedirs(self.img_dir) - - self.doc = dominate.document(title=title) - if refresh > 0: - with self.doc.head: - meta(http_equiv="refresh", content=str(refresh)) - - def get_image_dir(self): - return self.img_dir - - def add_header(self, str): - with self.doc: - h3(str) - - def add_table(self, border=1): - self.t = table(border=border, style="table-layout: fixed;") - self.doc.add(self.t) - - def add_images(self, ims, txts, links, width=512): - self.add_table() - with self.t: - with tr(): - for im, txt, link in zip(ims, txts, links): - with td(style="word-wrap: break-word;", halign="center", valign="top"): - with p(): - with a(href=os.path.join('images', link)): - img(style="width:%dpx" % (width), src=os.path.join('images', im)) - br() - p(txt) - - def save(self): - html_file = '%s/index.html' % self.web_dir - f = open(html_file, 'wt') - f.write(self.doc.render()) - f.close() - - -if __name__ == '__main__': - html = HTML('web/', 'test_html') - html.add_header('hello world') - - ims = [] - txts = [] - links = [] - for n in range(4): - ims.append('image_%d.jpg' % n) - txts.append('text_%d' % n) - links.append('image_%d.jpg' % n) - html.add_images(ims, txts, links) - html.save() diff --git a/pix2pixHD_attack/util/image_pool.py b/pix2pixHD_attack/util/image_pool.py deleted file mode 100755 index 63e1877..0000000 --- a/pix2pixHD_attack/util/image_pool.py +++ /dev/null @@ -1,31 +0,0 @@ -import random -import torch -from torch.autograd import Variable -class ImagePool(): - def __init__(self, pool_size): - self.pool_size = pool_size - if self.pool_size > 0: - self.num_imgs = 0 - self.images = [] - - def query(self, images): - if self.pool_size == 0: - return images - return_images = [] - for image in images.data: - image = torch.unsqueeze(image, 0) - if self.num_imgs < self.pool_size: - self.num_imgs = self.num_imgs + 1 - self.images.append(image) - return_images.append(image) - else: - p = random.uniform(0, 1) - if p > 0.5: - random_id = random.randint(0, self.pool_size-1) - tmp = self.images[random_id].clone() - self.images[random_id] = image - return_images.append(tmp) - else: - return_images.append(image) - return_images = Variable(torch.cat(return_images, 0)) - return return_images diff --git a/pix2pixHD_attack/util/util.py b/pix2pixHD_attack/util/util.py deleted file mode 100755 index f4f79ec..0000000 --- a/pix2pixHD_attack/util/util.py +++ /dev/null @@ -1,100 +0,0 @@ -from __future__ import print_function -import torch -import numpy as np -from PIL import Image -import numpy as np -import os - -# Converts a Tensor into a Numpy array -# |imtype|: the desired type of the converted numpy array -def tensor2im(image_tensor, imtype=np.uint8, normalize=True): - if isinstance(image_tensor, list): - image_numpy = [] - for i in range(len(image_tensor)): - image_numpy.append(tensor2im(image_tensor[i], imtype, normalize)) - return image_numpy - image_numpy = image_tensor.cpu().float().numpy() - if normalize: - image_numpy = (np.transpose(image_numpy, (1, 2, 0)) + 1) / 2.0 * 255.0 - else: - image_numpy = np.transpose(image_numpy, (1, 2, 0)) * 255.0 - image_numpy = np.clip(image_numpy, 0, 255) - if image_numpy.shape[2] == 1 or image_numpy.shape[2] > 3: - image_numpy = image_numpy[:,:,0] - return image_numpy.astype(imtype) - -# Converts a one-hot tensor into a colorful label map -def tensor2label(label_tensor, n_label, imtype=np.uint8): - if n_label == 0: - return tensor2im(label_tensor, imtype) - label_tensor = label_tensor.cpu().float() - if label_tensor.size()[0] > 1: - label_tensor = label_tensor.max(0, keepdim=True)[1] - label_tensor = Colorize(n_label)(label_tensor) - label_numpy = np.transpose(label_tensor.numpy(), (1, 2, 0)) - return label_numpy.astype(imtype) - -def save_image(image_numpy, image_path): - image_pil = Image.fromarray(image_numpy) - image_pil.save(image_path) - -def mkdirs(paths): - if isinstance(paths, list) and not isinstance(paths, str): - for path in paths: - mkdir(path) - else: - mkdir(paths) - -def mkdir(path): - if not os.path.exists(path): - os.makedirs(path) - -############################################################################### -# Code from -# https://github.com/ycszen/pytorch-seg/blob/master/transform.py -# Modified so it complies with the Citscape label map colors -############################################################################### -def uint82bin(n, count=8): - """returns the binary of integer n, count refers to amount of bits""" - return ''.join([str((n >> y) & 1) for y in range(count-1, -1, -1)]) - -def labelcolormap(N): - if N == 35: # cityscape - cmap = np.array([( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), ( 0, 0, 0), (111, 74, 0), ( 81, 0, 81), - (128, 64,128), (244, 35,232), (250,170,160), (230,150,140), ( 70, 70, 70), (102,102,156), (190,153,153), - (180,165,180), (150,100,100), (150,120, 90), (153,153,153), (153,153,153), (250,170, 30), (220,220, 0), - (107,142, 35), (152,251,152), ( 70,130,180), (220, 20, 60), (255, 0, 0), ( 0, 0,142), ( 0, 0, 70), - ( 0, 60,100), ( 0, 0, 90), ( 0, 0,110), ( 0, 80,100), ( 0, 0,230), (119, 11, 32), ( 0, 0,142)], - dtype=np.uint8) - else: - cmap = np.zeros((N, 3), dtype=np.uint8) - for i in range(N): - r, g, b = 0, 0, 0 - id = i - for j in range(7): - str_id = uint82bin(id) - r = r ^ (np.uint8(str_id[-1]) << (7-j)) - g = g ^ (np.uint8(str_id[-2]) << (7-j)) - b = b ^ (np.uint8(str_id[-3]) << (7-j)) - id = id >> 3 - cmap[i, 0] = r - cmap[i, 1] = g - cmap[i, 2] = b - return cmap - -class Colorize(object): - def __init__(self, n=35): - self.cmap = labelcolormap(n) - self.cmap = torch.from_numpy(self.cmap[:n]) - - def __call__(self, gray_image): - size = gray_image.size() - color_image = torch.ByteTensor(3, size[1], size[2]).fill_(0) - - for label in range(0, len(self.cmap)): - mask = (label == gray_image[0]).cpu() - color_image[0][mask] = self.cmap[label][0] - color_image[1][mask] = self.cmap[label][1] - color_image[2][mask] = self.cmap[label][2] - - return color_image diff --git a/pix2pixHD_attack/util/visualizer.py b/pix2pixHD_attack/util/visualizer.py deleted file mode 100755 index 584ac45..0000000 --- a/pix2pixHD_attack/util/visualizer.py +++ /dev/null @@ -1,131 +0,0 @@ -import numpy as np -import os -import ntpath -import time -from . import util -from . import html -import scipy.misc -try: - from StringIO import StringIO # Python 2.7 -except ImportError: - from io import BytesIO # Python 3.x - -class Visualizer(): - def __init__(self, opt): - # self.opt = opt - self.tf_log = opt.tf_log - self.use_html = opt.isTrain and not opt.no_html - self.win_size = opt.display_winsize - self.name = opt.name - if self.tf_log: - import tensorflow as tf - self.tf = tf - self.log_dir = os.path.join(opt.checkpoints_dir, opt.name, 'logs') - self.writer = tf.summary.FileWriter(self.log_dir) - - if self.use_html: - self.web_dir = os.path.join(opt.checkpoints_dir, opt.name, 'web') - self.img_dir = os.path.join(self.web_dir, 'images') - print('create web directory %s...' % self.web_dir) - util.mkdirs([self.web_dir, self.img_dir]) - self.log_name = os.path.join(opt.checkpoints_dir, opt.name, 'loss_log.txt') - with open(self.log_name, "a") as log_file: - now = time.strftime("%c") - log_file.write('================ Training Loss (%s) ================\n' % now) - - # |visuals|: dictionary of images to display or save - def display_current_results(self, visuals, epoch, step): - if self.tf_log: # show images in tensorboard output - img_summaries = [] - for label, image_numpy in visuals.items(): - # Write the image to a string - try: - s = StringIO() - except: - s = BytesIO() - scipy.misc.toimage(image_numpy).save(s, format="jpeg") - # Create an Image object - img_sum = self.tf.Summary.Image(encoded_image_string=s.getvalue(), height=image_numpy.shape[0], width=image_numpy.shape[1]) - # Create a Summary value - img_summaries.append(self.tf.Summary.Value(tag=label, image=img_sum)) - - # Create and write Summary - summary = self.tf.Summary(value=img_summaries) - self.writer.add_summary(summary, step) - - if self.use_html: # save images to a html file - for label, image_numpy in visuals.items(): - if isinstance(image_numpy, list): - for i in range(len(image_numpy)): - img_path = os.path.join(self.img_dir, 'epoch%.3d_%s_%d.jpg' % (epoch, label, i)) - util.save_image(image_numpy[i], img_path) - else: - img_path = os.path.join(self.img_dir, 'epoch%.3d_%s.jpg' % (epoch, label)) - util.save_image(image_numpy, img_path) - - # update website - webpage = html.HTML(self.web_dir, 'Experiment name = %s' % self.name, refresh=30) - for n in range(epoch, 0, -1): - webpage.add_header('epoch [%d]' % n) - ims = [] - txts = [] - links = [] - - for label, image_numpy in visuals.items(): - if isinstance(image_numpy, list): - for i in range(len(image_numpy)): - img_path = 'epoch%.3d_%s_%d.jpg' % (n, label, i) - ims.append(img_path) - txts.append(label+str(i)) - links.append(img_path) - else: - img_path = 'epoch%.3d_%s.jpg' % (n, label) - ims.append(img_path) - txts.append(label) - links.append(img_path) - if len(ims) < 10: - webpage.add_images(ims, txts, links, width=self.win_size) - else: - num = int(round(len(ims)/2.0)) - webpage.add_images(ims[:num], txts[:num], links[:num], width=self.win_size) - webpage.add_images(ims[num:], txts[num:], links[num:], width=self.win_size) - webpage.save() - - # errors: dictionary of error labels and values - def plot_current_errors(self, errors, step): - if self.tf_log: - for tag, value in errors.items(): - summary = self.tf.Summary(value=[self.tf.Summary.Value(tag=tag, simple_value=value)]) - self.writer.add_summary(summary, step) - - # errors: same format as |errors| of plotCurrentErrors - def print_current_errors(self, epoch, i, errors, t): - message = '(epoch: %d, iters: %d, time: %.3f) ' % (epoch, i, t) - for k, v in errors.items(): - if v != 0: - message += '%s: %.3f ' % (k, v) - - print(message) - with open(self.log_name, "a") as log_file: - log_file.write('%s\n' % message) - - # save image to the disk - def save_images(self, webpage, visuals, image_path): - image_dir = webpage.get_image_dir() - short_path = ntpath.basename(image_path[0]) - name = os.path.splitext(short_path)[0] - - webpage.add_header(name) - ims = [] - txts = [] - links = [] - - for label, image_numpy in visuals.items(): - image_name = '%s_%s.jpg' % (name, label) - save_path = os.path.join(image_dir, image_name) - util.save_image(image_numpy, save_path) - - ims.append(image_name) - txts.append(label) - links.append(image_name) - webpage.add_images(ims, txts, links, width=self.win_size) diff --git a/stargan/._.DS_Store b/stargan/._.DS_Store deleted file mode 100644 index b3f132f..0000000 Binary files a/stargan/._.DS_Store and /dev/null differ diff --git a/stargan/._1-images.jpg b/stargan/._1-images.jpg deleted file mode 100644 index 90a85e0..0000000 Binary files a/stargan/._1-images.jpg and /dev/null differ diff --git a/stargan/._2-images.jpg b/stargan/._2-images.jpg deleted file mode 100644 index 978a7f2..0000000 Binary files a/stargan/._2-images.jpg and /dev/null differ diff --git a/stargan/._README.md b/stargan/._README.md deleted file mode 100644 index 700298f..0000000 Binary files a/stargan/._README.md and /dev/null differ diff --git a/stargan/._attacks.py b/stargan/._attacks.py deleted file mode 100644 index 03c6c8d..0000000 Binary files a/stargan/._attacks.py and /dev/null differ diff --git a/stargan/._data_loader.py b/stargan/._data_loader.py deleted file mode 100644 index 27fdbd4..0000000 Binary files a/stargan/._data_loader.py and /dev/null differ diff --git a/stargan/._download.sh b/stargan/._download.sh deleted file mode 100644 index a20182c..0000000 Binary files a/stargan/._download.sh and /dev/null differ diff --git a/stargan/._logger.py b/stargan/._logger.py deleted file mode 100644 index b0f76fe..0000000 Binary files a/stargan/._logger.py and /dev/null differ diff --git a/stargan/._main.py b/stargan/._main.py deleted file mode 100644 index 8837ee6..0000000 Binary files a/stargan/._main.py and /dev/null differ diff --git a/stargan/._model.py b/stargan/._model.py deleted file mode 100644 index 1fe6baf..0000000 Binary files a/stargan/._model.py and /dev/null differ diff --git a/stargan/._noise.py b/stargan/._noise.py deleted file mode 100644 index 44fb386..0000000 Binary files a/stargan/._noise.py and /dev/null differ diff --git a/stargan/._solver.py b/stargan/._solver.py deleted file mode 100644 index 42b7dcc..0000000 Binary files a/stargan/._solver.py and /dev/null differ diff --git a/stargan/1-images.jpg b/stargan/1-images.jpg deleted file mode 100644 index a61d696..0000000 Binary files a/stargan/1-images.jpg and /dev/null differ diff --git a/stargan/2-images.jpg b/stargan/2-images.jpg deleted file mode 100644 index d798f2d..0000000 Binary files a/stargan/2-images.jpg and /dev/null differ diff --git a/stargan/__pycache__/attacks.cpython-37.pyc b/stargan/__pycache__/attacks.cpython-37.pyc deleted file mode 100644 index f209dca..0000000 Binary files a/stargan/__pycache__/attacks.cpython-37.pyc and /dev/null differ diff --git a/stargan/__pycache__/data_loader.cpython-37.pyc b/stargan/__pycache__/data_loader.cpython-37.pyc deleted file mode 100644 index e7b1766..0000000 Binary files a/stargan/__pycache__/data_loader.cpython-37.pyc and /dev/null differ diff --git a/stargan/__pycache__/logger.cpython-37.pyc b/stargan/__pycache__/logger.cpython-37.pyc deleted file mode 100644 index 9fb5d73..0000000 Binary files a/stargan/__pycache__/logger.cpython-37.pyc and /dev/null differ diff --git a/stargan/__pycache__/model.cpython-37.pyc b/stargan/__pycache__/model.cpython-37.pyc deleted file mode 100644 index 9459c8a..0000000 Binary files a/stargan/__pycache__/model.cpython-37.pyc and /dev/null differ diff --git a/stargan/__pycache__/noise.cpython-37.pyc b/stargan/__pycache__/noise.cpython-37.pyc deleted file mode 100644 index 7a7d920..0000000 Binary files a/stargan/__pycache__/noise.cpython-37.pyc and /dev/null differ diff --git a/stargan/__pycache__/solver.cpython-37.pyc b/stargan/__pycache__/solver.cpython-37.pyc deleted file mode 100644 index e12d25a..0000000 Binary files a/stargan/__pycache__/solver.cpython-37.pyc and /dev/null differ diff --git a/stargan/attacks.py b/stargan/attacks.py index 0d42ac7..e40410c 100644 --- a/stargan/attacks.py +++ b/stargan/attacks.py @@ -9,35 +9,47 @@ import torch.nn as nn import defenses.smoothing as smoothing class LinfPGDAttack(object): - def __init__(self, model=None, device=None, epsilon=0.05, k=30, a=0.01, feat = None): + def __init__(self, model=None, device=None, epsilon=0.05, k=10, a=0.01, feat = None): + """ + FGSM, I-FGSM and PGD attacks + epsilon: magnitude of attack + k: iterations + a: step size + """ self.model = model self.epsilon = epsilon self.k = k self.a = a self.loss_fn = nn.MSELoss().to(device) self.device = device + + # Feature-level attack? Which layer? self.feat = feat + # PGD or I-FGSM? + self.rand = True + def perturb(self, X_nat, y, c_trg): """ - Given examples (X_nat, y), returns adversarial - examples within epsilon of X_nat in l_infinity norm. + Vanilla Attack. """ - X = X_nat.clone().detach_() + if self.rand: + X = X_nat.clone().detach_() + torch.tensor(np.random.uniform(-self.epsilon, self.epsilon, X_nat.shape).astype('float32')).to(self.device) + else: + X = X_nat.clone().detach_() + # use the following if FGSM or I-FGSM and random seeds are fixed + # X = X_nat.clone().detach_() + torch.tensor(np.random.uniform(-0.001, 0.001, X_nat.shape).astype('float32')).cuda() for i in range(self.k): - # print(i) X.requires_grad = True output, feats = self.model(X, c_trg) if self.feat: - # print('self.feat ', self.feat) output = feats[self.feat] - y = np.zeros(output.shape) - y = torch.FloatTensor(y).to(self.device) self.model.zero_grad() - loss = -self.loss_fn(output, y) + # Minus in the loss means "towards" and plus means "away from" + loss = self.loss_fn(output, y) loss.backward() grad = X.grad @@ -52,14 +64,21 @@ class LinfPGDAttack(object): def perturb_blur(self, X_nat, y, c_trg): """ - Given examples (X_nat, y), returns adversarial - examples within epsilon of X_nat in l_infinity norm. + White-box attack against blur pre-processing. """ - X = X_nat.clone().detach_() + if self.rand: + X = X_nat.clone().detach_() + torch.tensor(np.random.uniform(-self.epsilon, self.epsilon, X_nat.shape).astype('float32')).to(self.device) + else: + X = X_nat.clone().detach_() + # use the following if FGSM or I-FGSM and random seeds are fixed + # X = X_nat.clone().detach_() + torch.tensor(np.random.uniform(-0.001, 0.001, X_nat.shape).astype('float32')).cuda() + X_orig = X_nat.clone().detach_() + # Kernel size ks = 11 - sig = 1 + # Sigma for Gaussian noise + sig = 1.5 # preproc = smoothing.AverageSmoothing2D(channels=3, kernel_size=ks).to(self.device) preproc = smoothing.GaussianSmoothing2D(sigma=sig, channels=3, kernel_size=ks).to(self.device) @@ -68,18 +87,11 @@ class LinfPGDAttack(object): blurred_image = smoothing.GaussianSmoothing2D(sigma=sig, channels=3, kernel_size=ks).to(self.device)(X_orig) for i in range(self.k): - # print(i) X.requires_grad = True output, feats = self.model.forward_blur(X, c_trg, preproc) - if self.feat: - # print('self.feat ', self.feat) - output = feats[self.feat] - y = np.zeros(output.shape) - y = torch.FloatTensor(y).to(self.device) - self.model.zero_grad() - loss = -self.loss_fn(output, y) + loss = self.loss_fn(output, y) loss.backward() grad = X.grad @@ -92,36 +104,41 @@ class LinfPGDAttack(object): return X, X - X_nat, blurred_image - def perturb_blur_iter(self, X_nat, y, c_trg): + def perturb_blur_iter_full(self, X_nat, y, c_trg): """ - Given examples (X_nat, y), returns adversarial - examples within epsilon of X_nat in l_infinity norm. + Spread-spectrum attack against blur defenses (gray-box scenario). """ - X = X_nat.clone().detach_() + if self.rand: + X = X_nat.clone().detach_() + torch.tensor(np.random.uniform(-self.epsilon, self.epsilon, X_nat.shape).astype('float32')).to(self.device) + else: + X = X_nat.clone().detach_() + # use the following if FGSM or I-FGSM and random seeds are fixed + # X = X_nat.clone().detach_() + torch.tensor(np.random.uniform(-0.001, 0.001, X_nat.shape).astype('float32')).cuda() + # Gaussian blur kernel size ks_gauss = 11 + # Average smoothing kernel size ks_avg = 3 + # Sigma for Gaussian blur sig = 1 + # Type of blur blur_type = 1 for i in range(self.k): # Declare smoothing layer if blur_type == 1: - preproc = smoothing.AverageSmoothing2D(channels=3, kernel_size=ks_avg).to(self.device) - elif blur_type == 2: preproc = smoothing.GaussianSmoothing2D(sigma=sig, channels=3, kernel_size=ks_gauss).to(self.device) + elif blur_type == 2: + preproc = smoothing.AverageSmoothing2D(channels=3, kernel_size=ks_avg).to(self.device) X.requires_grad = True output, feats = self.model.forward_blur(X, c_trg, preproc) if self.feat: - # print('self.feat ', self.feat) output = feats[self.feat] - y = np.zeros(output.shape) - y = torch.FloatTensor(y).to(self.device) self.model.zero_grad() - loss = -self.loss_fn(output, y) + loss = self.loss_fn(output, y) loss.backward() grad = X.grad @@ -130,24 +147,153 @@ class LinfPGDAttack(object): eta = torch.clamp(X_adv - X_nat, min=-self.epsilon, max=self.epsilon) X = torch.clamp(X_nat + eta, min=-1, max=1).detach_() + # Iterate through blur types if blur_type == 1: - sig += 0.2 - if sig == 3.2: + sig += 0.5 + if sig >= 3.2: blur_type = 2 sig = 1 if blur_type == 2: ks_avg += 2 - if ks_avg == 11: + if ks_avg >= 11: blur_type = 1 ks_avg = 3 - print(blur_type, sig, ks_avg) - - self.model.zero_grad() return X, X - X_nat + def perturb_blur_eot(self, X_nat, y, c_trg): + """ + EoT adaptation to the blur transformation. + """ + if self.rand: + X = X_nat.clone().detach_() + torch.tensor(np.random.uniform(-self.epsilon, self.epsilon, X_nat.shape).astype('float32')).to(self.device) + else: + X = X_nat.clone().detach_() + # use the following if FGSM or I-FGSM and random seeds are fixed + # X = X_nat.clone().detach_() + torch.tensor(np.random.uniform(-0.001, 0.001, X_nat.shape).astype('float32')).cuda() + + # Gaussian blur kernel size + ks_gauss = 11 + # Average smoothing kernel size + ks_avg = 3 + # Sigma for Gaussian blur + sig = 1 + # Type of blur + blur_type = 1 + + for i in range(self.k): + full_loss = 0.0 + X.requires_grad = True + self.model.zero_grad() + + for j in range(9): # 9 types of blur + # Declare smoothing layer + if blur_type == 1: + preproc = smoothing.GaussianSmoothing2D(sigma=sig, channels=3, kernel_size=ks_gauss).to(self.device) + elif blur_type == 2: + preproc = smoothing.AverageSmoothing2D(channels=3, kernel_size=ks_avg).to(self.device) + + + output, feats = self.model.forward_blur(X, c_trg, preproc) + + loss = self.loss_fn(output, y) + full_loss += loss + + if blur_type == 1: + sig += 0.5 + if sig >= 3.2: + blur_type = 2 + sig = 1 + if blur_type == 2: + ks_avg += 2 + if ks_avg >= 11: + blur_type = 1 + ks_avg = 3 + + full_loss.backward() + grad = X.grad + + X_adv = X + self.a * grad.sign() + + eta = torch.clamp(X_adv - X_nat, min=-self.epsilon, max=self.epsilon) + X = torch.clamp(X_nat + eta, min=-1, max=1).detach_() + + self.model.zero_grad() + + return X, X - X_nat + + + def perturb_iter_class(self, X_nat, y, c_trg): + """ + Iterative Class Conditional Attack + """ + if self.rand: + X = X_nat.clone().detach_() + torch.tensor(np.random.uniform(-self.epsilon, self.epsilon, X_nat.shape).astype('float32')).to(self.device) + else: + X = X_nat.clone().detach_() + # use the following if FGSM or I-FGSM and random seeds are fixed + # X = X_nat.clone().detach_() + torch.tensor(np.random.uniform(-0.001, 0.001, X_nat.shape).astype('float32')).cuda() + + j = 0 + J = len(c_trg) + + for i in range(self.k): + X.requires_grad = True + output, feats = self.model(X, c_trg[j]) + + self.model.zero_grad() + + loss = self.loss_fn(output, y) + loss.backward() + grad = X.grad + + X_adv = X + self.a * grad.sign() + + eta = torch.clamp(X_adv - X_nat, min=-self.epsilon, max=self.epsilon) + X = torch.clamp(X_nat + eta, min=-1, max=1).detach_() + + j += 1 + if j == J: + j = 0 + + return X, eta + + def perturb_joint_class(self, X_nat, y, c_trg): + """ + Joint Class Conditional Attack + """ + if self.rand: + X = X_nat.clone().detach_() + torch.tensor(np.random.uniform(-self.epsilon, self.epsilon, X_nat.shape).astype('float32')).to(self.device) + else: + X = X_nat.clone().detach_() + # use the following if FGSM or I-FGSM and random seeds are fixed + # X = X_nat.clone().detach_() + torch.tensor(np.random.uniform(-0.001, 0.001, X_nat.shape).astype('float32')).cuda() + + J = len(c_trg) + + for i in range(self.k): + full_loss = 0.0 + X.requires_grad = True + self.model.zero_grad() + + for j in range(J): + output, feats = self.model(X, c_trg[j]) + + loss = self.loss_fn(output, y) + full_loss += loss + + full_loss.backward() + grad = X.grad + + X_adv = X + self.a * grad.sign() + + eta = torch.clamp(X_adv - X_nat, min=-self.epsilon, max=self.epsilon) + X = torch.clamp(X_nat + eta, min=-1, max=1).detach_() + + return X, eta + def clip_tensor(X, Y, Z): # Clip X with Y min and Z max X_np = X.data.cpu().numpy() @@ -155,4 +301,17 @@ def clip_tensor(X, Y, Z): Z_np = Z.data.cpu().numpy() X_clipped = np.clip(X_np, Y_np, Z_np) X_res = torch.FloatTensor(X_clipped) - return X_res \ No newline at end of file + return X_res + +def perturb_batch(X, y, c_trg, model, adversary): + # Perturb batch function for adversarial training + model_cp = copy.deepcopy(model) + for p in model_cp.parameters(): + p.requires_grad = False + model_cp.eval() + + adversary.model = model_cp + + X_adv, _ = adversary.perturb(X, y, c_trg) + + return X_adv \ No newline at end of file diff --git a/stargan/defenses/__pycache__/__init__.cpython-37.pyc b/stargan/defenses/__pycache__/__init__.cpython-37.pyc index c41c5aa..5031ff7 100644 Binary files a/stargan/defenses/__pycache__/__init__.cpython-37.pyc and b/stargan/defenses/__pycache__/__init__.cpython-37.pyc differ diff --git a/stargan/defenses/__pycache__/base.cpython-37.pyc b/stargan/defenses/__pycache__/base.cpython-37.pyc index 317726f..a9195c8 100644 Binary files a/stargan/defenses/__pycache__/base.cpython-37.pyc and b/stargan/defenses/__pycache__/base.cpython-37.pyc differ diff --git a/stargan/defenses/__pycache__/smoothing.cpython-37.pyc b/stargan/defenses/__pycache__/smoothing.cpython-37.pyc index f0769db..37f4a28 100644 Binary files a/stargan/defenses/__pycache__/smoothing.cpython-37.pyc and b/stargan/defenses/__pycache__/smoothing.cpython-37.pyc differ diff --git a/stargan/main.py b/stargan/main.py index 597215c..0f5ec52 100644 --- a/stargan/main.py +++ b/stargan/main.py @@ -41,13 +41,24 @@ def main(config): if config.mode == 'train': if config.dataset in ['CelebA', 'RaFD']: - solver.train() + # Vanilla training + # solver.train() + # Generator adversarial training + # solver.train_adv_gen() + # G+D adversarial training + solver.train_adv_both() elif config.dataset in ['Both']: solver.train_multi() elif config.mode == 'test': if config.dataset in ['CelebA', 'RaFD']: + # Normal inference # solver.test() + # Attack inference solver.test_attack() + # Feature attack experiment + # solver.test_attack_feats() + # Conditional attack experiment + # solver.test_attack_cond() elif config.dataset in ['Both']: solver.test_multi() @@ -71,7 +82,7 @@ if __name__ == '__main__': # Training configuration. parser.add_argument('--dataset', type=str, default='CelebA', choices=['CelebA', 'RaFD', 'Both']) - parser.add_argument('--batch_size', type=int, default=1, help='mini-batch size') + parser.add_argument('--batch_size', type=int, default=13, help='mini-batch size') parser.add_argument('--num_iters', type=int, default=200000, help='number of total iterations for training D') parser.add_argument('--num_iters_decay', type=int, default=100000, help='number of iterations for decaying lr') parser.add_argument('--g_lr', type=float, default=0.0001, help='learning rate for G') @@ -103,7 +114,7 @@ if __name__ == '__main__': # Step size. parser.add_argument('--log_step', type=int, default=10) parser.add_argument('--sample_step', type=int, default=1000) - parser.add_argument('--model_save_step', type=int, default=10000) + parser.add_argument('--model_save_step', type=int, default=5000) parser.add_argument('--lr_update_step', type=int, default=1000) config = parser.parse_args() diff --git a/stargan/solver.py b/stargan/solver.py index af76cee..3d0ecc1 100644 --- a/stargan/solver.py +++ b/stargan/solver.py @@ -16,6 +16,11 @@ from torchvision import transforms import defenses.smoothing as smoothing +# torch.manual_seed(0) +# torch.backends.cudnn.deterministic = True +# torch.backends.cudnn.benchmark = False +np.random.seed(0) + class Solver(object): """Solver for training and testing StarGAN.""" @@ -112,6 +117,33 @@ class Solver(object): self.load_model_weights(self.G, G_path) self.D.load_state_dict(torch.load(D_path, map_location=lambda storage, loc: storage)) + def build_and_restore_alt_model(self): + """Create a generator and a discriminator.""" + if self.dataset in ['CelebA', 'RaFD']: + self.G2 = Generator(self.g_conv_dim, self.c_dim, self.g_repeat_num) + self.D2 = Discriminator(self.image_size, self.d_conv_dim, self.c_dim, self.d_repeat_num) + elif self.dataset in ['Both']: + self.G2 = Generator(self.g_conv_dim, self.c_dim+self.c2_dim+2, self.g_repeat_num) # 2 for mask vector. + self.D2 = Discriminator(self.image_size, self.d_conv_dim, self.c_dim+self.c2_dim, self.d_repeat_num) + + self.g_optimizer2 = torch.optim.Adam(self.G2.parameters(), self.g_lr, [self.beta1, self.beta2]) + self.d_optimizer2 = torch.optim.Adam(self.D2.parameters(), self.d_lr, [self.beta1, self.beta2]) + self.print_network(self.G2, 'G') + self.print_network(self.D2, 'D') + + self.G2.to(self.device) + self.D2.to(self.device) + """Restore the trained generator and discriminator.""" + resume_iters = 50000 + model_save_dir = 'stargan/models' + print('Loading the trained models from step {}...'.format(resume_iters)) + G_path = os.path.join(model_save_dir, '{}-G.ckpt'.format(resume_iters)) + D_path = os.path.join(model_save_dir, '{}-D.ckpt'.format(resume_iters)) + + # self.G.load_state_dict(torch.load(G_path, map_location=lambda storage, loc: storage)) + self.load_model_weights(self.G2, G_path) + self.D2.load_state_dict(torch.load(D_path, map_location=lambda storage, loc: storage)) + def load_model_weights(self, model, path): pretrained_dict = torch.load(path, map_location=lambda storage, loc: storage) model_dict = model.state_dict() @@ -200,7 +232,7 @@ class Solver(object): return F.cross_entropy(logit, target) def train(self): - """Train StarGAN within a single dataset.""" + """Vanilla Training of StarGAN within a single dataset.""" # Set data loader. if self.dataset == 'CelebA': data_loader = self.celeba_loader @@ -261,18 +293,18 @@ class Solver(object): # =================================================================================== # # Compute loss with real images. - out_src, out_cls = self.D(x_real) + out_src, out_cls = self.D(x_real) # No Attack d_loss_real = - torch.mean(out_src) d_loss_cls = self.classification_loss(out_cls, label_org, self.dataset) # Compute loss with fake images. - x_fake = self.G(x_real, c_trg) - out_src, out_cls = self.D(x_fake.detach()) + x_fake, _ = self.G(x_real, c_trg) # No Attack + out_src, out_cls = self.D(x_fake.detach()) # No Attack d_loss_fake = torch.mean(out_src) # Compute loss for gradient penalty. alpha = torch.rand(x_real.size(0), 1, 1, 1).to(self.device) - x_hat = (alpha * x_real.data + (1 - alpha) * x_fake.data).requires_grad_(True) + x_hat = (alpha * x_real.data + (1 - alpha) * x_fake.data).requires_grad_(True) # No Attack out_src, _ = self.D(x_hat) d_loss_gp = self.gradient_penalty(out_src, x_hat) @@ -294,14 +326,13 @@ class Solver(object): # =================================================================================== # if (i+1) % self.n_critic == 0: - # Original-to-target domain. - x_fake = self.G(x_real, c_trg) + x_fake, _ = self.G(x_real, c_trg) # No Attack out_src, out_cls = self.D(x_fake) g_loss_fake = - torch.mean(out_src) g_loss_cls = self.classification_loss(out_cls, label_trg, self.dataset) # Target-to-original domain. - x_reconst = self.G(x_fake, c_org) + x_reconst, _ = self.G(x_fake, c_org) # No Attack g_loss_rec = torch.mean(torch.abs(x_real - x_reconst)) # Backward and optimize. @@ -337,7 +368,346 @@ class Solver(object): with torch.no_grad(): x_fake_list = [x_fixed] for c_fixed in c_fixed_list: - x_fake_list.append(self.G(x_fixed, c_fixed)) + elt, _ = self.G(x_fixed, c_fixed) + x_fake_list.append(elt) + x_concat = torch.cat(x_fake_list, dim=3) + sample_path = os.path.join(self.sample_dir, '{}-images.jpg'.format(i+1)) + save_image(self.denorm(x_concat.data.cpu()), sample_path, nrow=1, padding=0) + print('Saved real and fake images into {}...'.format(sample_path)) + + # Save model checkpoints. + if (i+1) % self.model_save_step == 0: + G_path = os.path.join(self.model_save_dir, '{}-G.ckpt'.format(i+1)) + D_path = os.path.join(self.model_save_dir, '{}-D.ckpt'.format(i+1)) + torch.save(self.G.state_dict(), G_path) + torch.save(self.D.state_dict(), D_path) + print('Saved model checkpoints into {}...'.format(self.model_save_dir)) + + # Decay learning rates. + if (i+1) % self.lr_update_step == 0 and (i+1) > (self.num_iters - self.num_iters_decay): + g_lr -= (self.g_lr / float(self.num_iters_decay)) + d_lr -= (self.d_lr / float(self.num_iters_decay)) + self.update_lr(g_lr, d_lr) + print ('Decayed learning rates, g_lr: {}, d_lr: {}.'.format(g_lr, d_lr)) + + def train_adv_gen(self): + """Adversarial Training for StarGAN only for Generator, within a single dataset.""" + # Set data loader. + if self.dataset == 'CelebA': + data_loader = self.celeba_loader + elif self.dataset == 'RaFD': + data_loader = self.rafd_loader + + # Fetch fixed inputs for debugging. + data_iter = iter(data_loader) + x_fixed, c_org = next(data_iter) + x_fixed = x_fixed.to(self.device) + c_fixed_list = self.create_labels(c_org, self.c_dim, self.dataset, self.selected_attrs) + + # Learning rate cache for decaying. + g_lr = self.g_lr + d_lr = self.d_lr + + # Start training from scratch or resume training. + start_iters = 0 + if self.resume_iters: + start_iters = self.resume_iters + self.restore_model(self.resume_iters) + + # Start training. + print('Start training...') + start_time = time.time() + for i in range(start_iters, self.num_iters): + + # =================================================================================== # + # 1. Preprocess input data # + # =================================================================================== # + + # Fetch real images and labels. + try: + x_real, label_org = next(data_iter) + except: + data_iter = iter(data_loader) + x_real, label_org = next(data_iter) + + # Generate target domain labels randomly. + rand_idx = torch.randperm(label_org.size(0)) + label_trg = label_org[rand_idx] + + if self.dataset == 'CelebA': + c_org = label_org.clone() + c_trg = label_trg.clone() + elif self.dataset == 'RaFD': + c_org = self.label2onehot(label_org, self.c_dim) + c_trg = self.label2onehot(label_trg, self.c_dim) + + x_real = x_real.to(self.device) # Input images. + c_org = c_org.to(self.device) # Original domain labels. + c_trg = c_trg.to(self.device) # Target domain labels. + label_org = label_org.to(self.device) # Labels for computing classification loss. + label_trg = label_trg.to(self.device) # Labels for computing classification loss. + + pgd_attack = attacks.LinfPGDAttack(model=self.G, device=self.device, feat=None) + # =================================================================================== # + # 2. Train the discriminator # + # =================================================================================== # + + # Compute loss with real images. + out_src, out_cls = self.D(x_real) # No Attack + d_loss_real = - torch.mean(out_src) + d_loss_cls = self.classification_loss(out_cls, label_org, self.dataset) + + # Compute loss with fake images. + x_fake, _ = self.G(x_real, c_trg) # No Attack + out_src, out_cls = self.D(x_fake.detach()) # No Attack + d_loss_fake = torch.mean(out_src) + + # Compute loss for gradient penalty. + alpha = torch.rand(x_real.size(0), 1, 1, 1).to(self.device) + x_hat = (alpha * x_real.data + (1 - alpha) * x_fake.data).requires_grad_(True) # No Attack + out_src, _ = self.D(x_hat) + d_loss_gp = self.gradient_penalty(out_src, x_hat) + + # Backward and optimize. + d_loss = d_loss_real + d_loss_fake + self.lambda_cls * d_loss_cls + self.lambda_gp * d_loss_gp + self.reset_grad() + d_loss.backward() + self.d_optimizer.step() + + # Logging. + loss = {} + loss['D/loss_real'] = d_loss_real.item() + loss['D/loss_fake'] = d_loss_fake.item() + loss['D/loss_cls'] = d_loss_cls.item() + loss['D/loss_gp'] = d_loss_gp.item() + + # =================================================================================== # + # 3. Train the generator # + # =================================================================================== # + + # Black image + black = np.zeros((x_real.shape[0],3,256,256)) + black = torch.FloatTensor(black).to(self.device) + + if (i+1) % self.n_critic == 0: + # Original-to-target domain. + pgd_attack = attacks.LinfPGDAttack(model=self.G, device=self.device, feat=None) + x_real_adv = attacks.perturb_batch(x_real, black, c_trg, self.G, pgd_attack) + + x_fake, _ = self.G(x_real_adv, c_trg) # Attack + out_src, out_cls = self.D(x_fake) + g_loss_fake = - torch.mean(out_src) + g_loss_cls = self.classification_loss(out_cls, label_trg, self.dataset) + + # Target-to-original domain. + x_fake_adv = attacks.perturb_batch(x_fake, black, c_org, self.G, pgd_attack) + x_reconst, _ = self.G(x_fake_adv, c_org) # Attack + g_loss_rec = torch.mean(torch.abs(x_real - x_reconst)) + + # Backward and optimize. + g_loss = g_loss_fake + self.lambda_rec * g_loss_rec + self.lambda_cls * g_loss_cls + self.reset_grad() + g_loss.backward() + self.g_optimizer.step() + + # Logging. + loss['G/loss_fake'] = g_loss_fake.item() + loss['G/loss_rec'] = g_loss_rec.item() + loss['G/loss_cls'] = g_loss_cls.item() + + # =================================================================================== # + # 4. Miscellaneous # + # =================================================================================== # + + # Print out training information. + if (i+1) % self.log_step == 0: + et = time.time() - start_time + et = str(datetime.timedelta(seconds=et))[:-7] + log = "Elapsed [{}], Iteration [{}/{}]".format(et, i+1, self.num_iters) + for tag, value in loss.items(): + log += ", {}: {:.4f}".format(tag, value) + print(log) + + if self.use_tensorboard: + for tag, value in loss.items(): + self.logger.scalar_summary(tag, value, i+1) + + # Translate fixed images for debugging. + if (i+1) % self.sample_step == 0: + with torch.no_grad(): + x_fake_list = [x_fixed] + for c_fixed in c_fixed_list: + elt, _ = self.G(x_fixed, c_fixed) + x_fake_list.append(elt) + x_concat = torch.cat(x_fake_list, dim=3) + sample_path = os.path.join(self.sample_dir, '{}-images.jpg'.format(i+1)) + save_image(self.denorm(x_concat.data.cpu()), sample_path, nrow=1, padding=0) + print('Saved real and fake images into {}...'.format(sample_path)) + + # Save model checkpoints. + if (i+1) % self.model_save_step == 0: + G_path = os.path.join(self.model_save_dir, '{}-G.ckpt'.format(i+1)) + D_path = os.path.join(self.model_save_dir, '{}-D.ckpt'.format(i+1)) + torch.save(self.G.state_dict(), G_path) + torch.save(self.D.state_dict(), D_path) + print('Saved model checkpoints into {}...'.format(self.model_save_dir)) + + # Decay learning rates. + if (i+1) % self.lr_update_step == 0 and (i+1) > (self.num_iters - self.num_iters_decay): + g_lr -= (self.g_lr / float(self.num_iters_decay)) + d_lr -= (self.d_lr / float(self.num_iters_decay)) + self.update_lr(g_lr, d_lr) + print ('Decayed learning rates, g_lr: {}, d_lr: {}.'.format(g_lr, d_lr)) + + def train_adv_both(self): + """G+D Adversarial Training for StarGAN with both Discriminator and Generator, within a single dataset.""" + # Set data loader. + if self.dataset == 'CelebA': + data_loader = self.celeba_loader + elif self.dataset == 'RaFD': + data_loader = self.rafd_loader + + # Fetch fixed inputs for debugging. + data_iter = iter(data_loader) + x_fixed, c_org = next(data_iter) + x_fixed = x_fixed.to(self.device) + c_fixed_list = self.create_labels(c_org, self.c_dim, self.dataset, self.selected_attrs) + + # Learning rate cache for decaying. + g_lr = self.g_lr + d_lr = self.d_lr + + # Start training from scratch or resume training. + start_iters = 0 + if self.resume_iters: + start_iters = self.resume_iters + self.restore_model(self.resume_iters) + + # Start training. + print('Start training...') + start_time = time.time() + for i in range(start_iters, self.num_iters): + + # =================================================================================== # + # 1. Preprocess input data # + # =================================================================================== # + + # Fetch real images and labels. + try: + x_real, label_org = next(data_iter) + except: + data_iter = iter(data_loader) + x_real, label_org = next(data_iter) + + # Generate target domain labels randomly. + rand_idx = torch.randperm(label_org.size(0)) + label_trg = label_org[rand_idx] + + if self.dataset == 'CelebA': + c_org = label_org.clone() + c_trg = label_trg.clone() + elif self.dataset == 'RaFD': + c_org = self.label2onehot(label_org, self.c_dim) + c_trg = self.label2onehot(label_trg, self.c_dim) + + x_real = x_real.to(self.device) # Input images. + c_org = c_org.to(self.device) # Original domain labels. + c_trg = c_trg.to(self.device) # Target domain labels. + label_org = label_org.to(self.device) # Labels for computing classification loss. + label_trg = label_trg.to(self.device) # Labels for computing classification loss. + + # Black image + black = np.zeros((x_real.shape[0],3,256,256)) + black = torch.FloatTensor(black).to(self.device) + + pgd_attack = attacks.LinfPGDAttack(model=self.G, device=self.device, feat=None) + x_real_adv = attacks.perturb_batch(x_real, black, c_trg, self.G, pgd_attack) # Adversarial training + + # =================================================================================== # + # 2. Train the discriminator # + # =================================================================================== # + + # Compute loss with real images. + out_src, out_cls = self.D(x_real_adv) # Attack + d_loss_real = - torch.mean(out_src) + d_loss_cls = self.classification_loss(out_cls, label_org, self.dataset) + + # Compute loss with fake images. + x_fake, _ = self.G(x_real_adv, c_trg) # Attack + x_fake_adv = attacks.perturb_batch(x_fake, black, c_org, self.G, pgd_attack) # Adversarial training + out_src, out_cls = self.D(x_fake_adv.detach()) # Attack + d_loss_fake = torch.mean(out_src) + + # Compute loss for gradient penalty. + alpha = torch.rand(x_real.size(0), 1, 1, 1).to(self.device) + x_hat = (alpha * x_real_adv.data + (1 - alpha) * x_fake_adv.data).requires_grad_(True) # Attack + out_src, _ = self.D(x_hat) + d_loss_gp = self.gradient_penalty(out_src, x_hat) + + # Backward and optimize. + d_loss = d_loss_real + d_loss_fake + self.lambda_cls * d_loss_cls + self.lambda_gp * d_loss_gp + self.reset_grad() + d_loss.backward() + self.d_optimizer.step() + + # Logging. + loss = {} + loss['D/loss_real'] = d_loss_real.item() + loss['D/loss_fake'] = d_loss_fake.item() + loss['D/loss_cls'] = d_loss_cls.item() + loss['D/loss_gp'] = d_loss_gp.item() + + # =================================================================================== # + # 3. Train the generator # + # =================================================================================== # + + if (i+1) % self.n_critic == 0: + # Original-to-target domain + x_fake, _ = self.G(x_real_adv, c_trg) # Attack + out_src, out_cls = self.D(x_fake) + g_loss_fake = - torch.mean(out_src) + g_loss_cls = self.classification_loss(out_cls, label_trg, self.dataset) + + # Target-to-original domain. + x_fake_adv = attacks.perturb_batch(x_fake, black, c_org, self.G, pgd_attack) + x_reconst, _ = self.G(x_fake_adv, c_org) # Attack + g_loss_rec = torch.mean(torch.abs(x_real - x_reconst)) + + # Backward and optimize. + g_loss = g_loss_fake + self.lambda_rec * g_loss_rec + self.lambda_cls * g_loss_cls + self.reset_grad() + g_loss.backward() + self.g_optimizer.step() + + # Logging. + loss['G/loss_fake'] = g_loss_fake.item() + loss['G/loss_rec'] = g_loss_rec.item() + loss['G/loss_cls'] = g_loss_cls.item() + + # =================================================================================== # + # 4. Miscellaneous # + # =================================================================================== # + + # Print out training information. + if (i+1) % self.log_step == 0: + et = time.time() - start_time + et = str(datetime.timedelta(seconds=et))[:-7] + log = "Elapsed [{}], Iteration [{}/{}]".format(et, i+1, self.num_iters) + for tag, value in loss.items(): + log += ", {}: {:.4f}".format(tag, value) + print(log) + + if self.use_tensorboard: + for tag, value in loss.items(): + self.logger.scalar_summary(tag, value, i+1) + + # Translate fixed images for debugging. + if (i+1) % self.sample_step == 0: + with torch.no_grad(): + x_fake_list = [x_fixed] + for c_fixed in c_fixed_list: + elt, _ = self.G(x_fixed, c_fixed) + x_fake_list.append(elt) x_concat = torch.cat(x_fake_list, dim=3) sample_path = os.path.join(self.sample_dir, '{}-images.jpg'.format(i+1)) save_image(self.denorm(x_concat.data.cpu()), sample_path, nrow=1, padding=0) @@ -359,7 +729,7 @@ class Solver(object): print ('Decayed learning rates, g_lr: {}, d_lr: {}.'.format(g_lr, d_lr)) def train_multi(self): - """Train StarGAN with multiple datasets.""" + """Vanilla Training for StarGAN with multiple datasets.""" # Data iterators. celeba_iter = iter(self.celeba_loader) rafd_iter = iter(self.rafd_loader) @@ -541,7 +911,7 @@ class Solver(object): print ('Decayed learning rates, g_lr: {}, d_lr: {}.'.format(g_lr, d_lr)) def test(self): - """Translate images using StarGAN trained on a single dataset.""" + """Translate images using StarGAN trained on a single dataset. No attack.""" # Load the trained generator. self.restore_model(self.test_iters) @@ -569,13 +939,11 @@ class Solver(object): save_image(self.denorm(x_concat.data.cpu()), result_path, nrow=1, padding=0) print('Saved real and fake images into {}...'.format(result_path)) + + def test_attack(self): - """Translate images using StarGAN trained on a single dataset.""" + """Vanilla or blur attacks.""" - layer_dict = {0: 2, 1: 5, 2: 8, 3: 9, 4: 10, 5: 11, 6: 12, 7: 13, 8: 14, 9: 17, 10: 20, 11: None} - torch.manual_seed(0) - - # for layer_num_orig in range(12): # Load the trained generator. self.restore_model(self.test_iters) @@ -586,98 +954,76 @@ class Solver(object): data_loader = self.rafd_loader # Initialize Metrics - l1_error = 0.0 - l2_error = 0.0 - min_dist = 0.0 - l0_error = 0.0 - perceptual_error = 0.0 - n_samples = 0 + l1_error, l2_error, min_dist, l0_error = 0.0, 0.0, 0.0, 0.0 + n_dist, n_samples = 0, 0 for i, (x_real, c_org) in enumerate(data_loader): - # Black image - black = np.zeros((1,3,256,256)) - black = torch.FloatTensor(black).to(self.device) - - # black = torch.FloatTensor(torch.rand((1,3,256,256))).to(self.device) - # Prepare input images and target domain labels. x_real = x_real.to(self.device) c_trg_list = self.create_labels(c_org, self.c_dim, self.dataset, self.selected_attrs) pgd_attack = attacks.LinfPGDAttack(model=self.G, device=self.device, feat=None) - # Translate images. + # Translated images. x_fake_list = [x_real] - - # x_advs = [] - # if i == 0: - # _, perturb = pgd_attack.perturb(x_real, black, c_trg_list[0]) - # for idx, c_trg in enumerate(c_trg_list): - # x_adv, perturb = pgd_attack.perturb(x_real, black, c_trg) - # x_advs.append((x_adv, perturb)) - # break for idx, c_trg in enumerate(c_trg_list): + print('image', i, 'class', idx) with torch.no_grad(): x_real_mod = x_real - x_real_mod = self.blur_tensor(x_real_mod) + # x_real_mod = self.blur_tensor(x_real_mod) # use blur gen_noattack, gen_noattack_feats = self.G(x_real_mod, c_trg) - # Attack - # x_adv, perturb = pgd_attack.perturb(x_real, black, c_trg) - # x_adv, perturb, blurred_image = pgd_attack.perturb_blur(x_real, black, c_trg) - x_adv, perturb = pgd_attack.perturb_blur_iter(x_real, black, c_trg) - # _, perturb = x_advs[idx] - # This next line is key for blur defense + # Attacks + x_adv, perturb = pgd_attack.perturb(x_real, gen_noattack, c_trg) # Vanilla attack + # x_adv, perturb, blurred_image = pgd_attack.perturb_blur(x_real, gen_noattack, c_trg) # White-box attack on blur + # x_adv, perturb = pgd_attack.perturb_blur_iter_full(x_real, gen_noattack, c_trg) # Spread-spectrum attack on blur + # x_adv, perturb = pgd_attack.perturb_blur_eot(x_real, gen_noattack, c_trg) # EoT blur adaptation + + # Generate adversarial example x_adv = x_real + perturb - # x_adv = torch.clamp(x_real + perturb, min=-1, max=1) - # TODO: Blurring here. - x_adv = self.blur_tensor(x_adv) + # No attack + # x_adv = x_real + + # x_adv = self.blur_tensor(x_adv) # use blur # Metrics with torch.no_grad(): - gen, preproc_x = self.G(x_adv, c_trg) - # gen, gen_feats = self.G(x_adv, c_trg) + gen, _ = self.G(x_adv, c_trg) # Add to lists - # x_fake_list.append(preproc_x) # x_fake_list.append(blurred_image) x_fake_list.append(x_adv) # x_fake_list.append(perturb) x_fake_list.append(gen) - # No Attack - # gen_noattack, _ = self.G(x_real, c_trg) - # gen_noattack, gen_noattack_feats = self.G(x_real, c_trg) - l1_error += F.l1_loss(gen, gen_noattack) l2_error += F.mse_loss(gen, gen_noattack) l0_error += (gen - gen_noattack).norm(0) min_dist += (gen - gen_noattack).norm(float('-inf')) + if F.mse_loss(gen, gen_noattack) > 0.05: + n_dist += 1 n_samples += 1 - # break # Save the translated images. x_concat = torch.cat(x_fake_list, dim=3) result_path = os.path.join(self.result_dir, '{}-images.jpg'.format(i+1)) save_image(self.denorm(x_concat.data.cpu()), result_path, nrow=1, padding=0) - # print('Saved real and fake images into {}...'.format(result_path)) - # if i == 3: - # break - if i == 49: + if i == 49: # stop after this many images break # Print metrics - print('{} images. L1 error: {}. L2 error: {}. L0 error: {}. L_-inf error: {}. Perceptual error: {}.'.format(n_samples, - l1_error / n_samples, l2_error / n_samples, l0_error / n_samples, min_dist / n_samples, perceptual_error / n_samples)) + print('{} images. L1 error: {}. L2 error: {}. prop_dist: {}. L0 error: {}. L_-inf error: {}.'.format(n_samples, + l1_error / n_samples, l2_error / n_samples, float(n_dist) / n_samples, l0_error / n_samples, min_dist / n_samples)) def test_attack_feats(self): - """Translate images using StarGAN trained on a single dataset.""" + """Feature-level attacks""" + # Mapping of feature layers to indices layer_dict = {0: 2, 1: 5, 2: 8, 3: 9, 4: 10, 5: 11, 6: 12, 7: 13, 8: 14, 9: 17, 10: 20, 11: None} - for layer_num_orig in range(12): + for layer_num_orig in range(12): # 11 layers + output # Load the trained generator. self.restore_model(self.test_iters) @@ -688,75 +1034,135 @@ class Solver(object): data_loader = self.rafd_loader # Initialize Metrics - l1_error = 0.0 - l2_error = 0.0 - min_dist = 0.0 - l0_error = 0.0 - perceptual_error = 0.0 - n_samples = 0 - - # 11 layers + output - # layer_num_orig = 11 + l1_error, l2_error, min_dist, l0_error = 0.0, 0.0, 0.0, 0.0 + n_dist, n_samples = 0, 0 print('Layer', layer_num_orig) - for i, (x_real, c_org) in enumerate(data_loader): - # Black image - black = np.zeros((1,3,256,256)) - black = torch.FloatTensor(black).to(self.device) + for i, (x_real, c_org) in enumerate(data_loader): # Prepare input images and target domain labels. x_real = x_real.to(self.device) c_trg_list = self.create_labels(c_org, self.c_dim, self.dataset, self.selected_attrs) - layer_num = layer_dict[layer_num_orig] + layer_num = layer_dict[layer_num_orig] # get layer number pgd_attack = attacks.LinfPGDAttack(model=self.G, device=self.device, feat=layer_num) # Translate images. x_fake_list = [x_real] - # if i == 0: - # x_adv, perturb = pgd_attack.perturb(x_real, x_real, c_trg_list[0]) - for c_trg in c_trg_list: + with torch.no_grad(): + gen_noattack, gen_noattack_feats = self.G(x_real, c_trg) + # Attack - x_adv, perturb = pgd_attack.perturb(x_real, black, c_trg) - # x_adv = x_real + perturb - # x_adv = self.blur_tensor(x_adv) + if layer_num == None: + x_adv, perturb = pgd_attack.perturb(x_real, gen_noattack, c_trg) + else: + x_adv, perturb = pgd_attack.perturb(x_real, gen_noattack_feats[layer_num], c_trg) + + x_adv = x_real + perturb # Metrics with torch.no_grad(): - # gen, preproc_x = self.G(x_adv, c_trg) gen, gen_feats = self.G(x_adv, c_trg) # Add to lists - # x_fake_list.append(preproc_x) x_fake_list.append(x_adv) x_fake_list.append(gen) - # No Attack - # gen_noattack, _ = self.G(x_real, c_trg) - gen_noattack, gen_noattack_feats = self.G(x_real, c_trg) - l1_error += F.l1_loss(gen, gen_noattack) l2_error += F.mse_loss(gen, gen_noattack) l0_error += (gen - gen_noattack).norm(0) min_dist += (gen - gen_noattack).norm(float('-inf')) + if F.mse_loss(gen, gen_noattack) > 0.05: + n_dist += 1 n_samples += 1 # Save the translated images. x_concat = torch.cat(x_fake_list, dim=3) - # result_path = os.path.join(self.result_dir, '{}-images.jpg'.format(i+1)) result_path = os.path.join(self.result_dir, '{}-{}-images.jpg'.format(layer_num_orig, i+1)) save_image(self.denorm(x_concat.data.cpu()), result_path, nrow=1, padding=0) - # print('Saved real and fake images into {}...'.format(result_path)) - if i == 3: + if i == 49: break - # if i == 199: - # break # Print metrics - print('{} images. L1 error: {}. L2 error: {}. L0 error: {}. L_-inf error: {}. Perceptual error: {}.'.format(n_samples, - l1_error / n_samples, l2_error / n_samples, l0_error / n_samples, min_dist / n_samples, perceptual_error / n_samples)) + print('{} images. L1 error: {}. L2 error: {}. prop_dist: {}. L0 error: {}. L_-inf error: {}.'.format(n_samples, + l1_error / n_samples, l2_error / n_samples, float(n_dist) / n_samples, l0_error / n_samples, min_dist / n_samples)) + + def test_attack_cond(self): + """Class conditional transfer""" + # Load the trained generator. + self.restore_model(self.test_iters) + + # Set data loader. + if self.dataset == 'CelebA': + data_loader = self.celeba_loader + elif self.dataset == 'RaFD': + data_loader = self.rafd_loader + + # Initialize Metrics + l1_error, l2_error, min_dist, l0_error = 0.0, 0.0, 0.0, 0.0 + n_dist, n_samples = 0, 0 + + for i, (x_real, c_org) in enumerate(data_loader): + # Prepare input images and target domain labels. + x_real = x_real.to(self.device) + c_trg_list = self.create_labels(c_org, self.c_dim, self.dataset, self.selected_attrs) + + pgd_attack = attacks.LinfPGDAttack(model=self.G, device=self.device, feat=None) + + # Translate images. + x_fake_list = [x_real] + + for idx, c_trg in enumerate(c_trg_list): + print(i, idx) + with torch.no_grad(): + x_real_mod = x_real + gen_noattack, gen_noattack_feats = self.G(x_real_mod, c_trg) + + # Transfer to different classes + if idx == 0: + # Wrong Class + x_adv, perturb = pgd_attack.perturb(x_real, gen_noattack, c_trg_list[0]) + + # Joint Class Conditional + # x_adv, perturb = pgd_attack.perturb_joint_class(x_real, gen_noattack, c_trg_list) + + # Iterative Class Conditional + # x_adv, perturb = pgd_attack.perturb_iter_class(x_real, gen_noattack, c_trg_list) + + # Correct Class + # x_adv, perturb = pgd_attack.perturb(x_real, gen_noattack, c_trg) + + x_adv = x_real + perturb + + # Metrics + with torch.no_grad(): + gen, _ = self.G(x_adv, c_trg) + + # Add to lists + x_fake_list.append(x_adv) + # x_fake_list.append(perturb) + x_fake_list.append(gen) + + l1_error += F.l1_loss(gen, gen_noattack) + l2_error += F.mse_loss(gen, gen_noattack) + l0_error += (gen - gen_noattack).norm(0) + min_dist += (gen - gen_noattack).norm(float('-inf')) + if F.mse_loss(gen, gen_noattack) > 0.05: + n_dist += 1 + n_samples += 1 + + # Save the translated images. + x_concat = torch.cat(x_fake_list, dim=3) + result_path = os.path.join(self.result_dir, '{}-images.jpg'.format(i+1)) + save_image(self.denorm(x_concat.data.cpu()), result_path, nrow=1, padding=0) + if i == 49: + break + + # Print metrics + print('{} images. L1 error: {}. L2 error: {}. prop_dist: {}. L0 error: {}. L_-inf error: {}.'.format(n_samples, + l1_error / n_samples, l2_error / n_samples, float(n_dist) / n_samples, l0_error / n_samples, min_dist / n_samples)) def test_multi(self): """Translate images using StarGAN trained on multiple datasets.""" @@ -790,18 +1196,7 @@ class Solver(object): save_image(self.denorm(x_concat.data.cpu()), result_path, nrow=1, padding=0) print('Saved real and fake images into {}...'.format(result_path)) - # def blur_tensor(self, tensor): - # # PIL to numpy - # img = self.denorm(tensor[0].data.cpu()) - # img = transforms.ToPILImage()(img) - # img = img.filter(ImageFilter.GaussianBlur(radius=1.5)) - # # img = img.filter(ImageFilter.BoxBlur(radius=2)) - # img = transforms.ToTensor()(img) - # img = transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))(img) - # img = torch.unsqueeze(img, 0).to(self.device) - # return img - def blur_tensor(self, tensor): - # preproc = smoothing.AverageSmoothing2D(channels=3, kernel_size=5).to(self.device) - preproc = smoothing.GaussianSmoothing2D(sigma=2, channels=3, kernel_size=11).to(self.device) - return preproc(tensor) \ No newline at end of file + # preproc = smoothing.AverageSmoothing2D(channels=3, kernel_size=9).to(self.device) + preproc = smoothing.GaussianSmoothing2D(sigma=1.5, channels=3, kernel_size=11).to(self.device) + return preproc(tensor) diff --git a/stargan/stargan_celeba_256 b/stargan/stargan_celeba_256 index c5f97bb..2167052 120000 --- a/stargan/stargan_celeba_256 +++ b/stargan/stargan_celeba_256 @@ -1 +1 @@ -/scratch2/fsynth/stargan/stargan_celeba_256 \ No newline at end of file +/home/grad3/nruiz9/research/fsynth/stargan/stargan_celeba_256 \ No newline at end of file diff --git a/stargan/vgg_loss.py b/stargan/vgg_loss.py deleted file mode 100644 index c8555c4..0000000 --- a/stargan/vgg_loss.py +++ /dev/null @@ -1,51 +0,0 @@ -import torch -import torch.nn as nn -import numpy as np - -from torchvision import models - -class Vgg19(torch.nn.Module): - def __init__(self, requires_grad=False): - super(Vgg19, self).__init__() - vgg_pretrained_features = models.vgg19(pretrained=True).features - self.slice1 = torch.nn.Sequential() - self.slice2 = torch.nn.Sequential() - self.slice3 = torch.nn.Sequential() - self.slice4 = torch.nn.Sequential() - self.slice5 = torch.nn.Sequential() - for x in range(2): - self.slice1.add_module(str(x), vgg_pretrained_features[x]) - for x in range(2, 7): - self.slice2.add_module(str(x), vgg_pretrained_features[x]) - for x in range(7, 12): - self.slice3.add_module(str(x), vgg_pretrained_features[x]) - for x in range(12, 21): - self.slice4.add_module(str(x), vgg_pretrained_features[x]) - for x in range(21, 30): - self.slice5.add_module(str(x), vgg_pretrained_features[x]) - if not requires_grad: - for param in self.parameters(): - param.requires_grad = False - - def forward(self, X): - h_relu1 = self.slice1(X) - h_relu2 = self.slice2(h_relu1) - h_relu3 = self.slice3(h_relu2) - h_relu4 = self.slice4(h_relu3) - h_relu5 = self.slice5(h_relu4) - out = [h_relu1, h_relu2, h_relu3, h_relu4, h_relu5] - return out - -class VGGLoss(nn.Module): - def __init__(self, gpu_ids): - super(VGGLoss, self).__init__() - self.vgg = Vgg19().cuda() - self.criterion = nn.L1Loss() - self.weights = [0.0, 0.0, 0.0, 0.0, 1.0] - - def forward(self, x, y): - x_vgg, y_vgg = self.vgg(x), self.vgg(y) - loss = 0 - for i in range(len(x_vgg)): - loss += self.weights[i] * self.criterion(x_vgg[i], y_vgg[i].detach()) - return loss \ No newline at end of file