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